git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [JGIT PATCH 0/5] Add "jgit upload-pack" for fetch service
@ 2008-12-23 22:56 Shawn O. Pearce
  2008-12-23 22:56 ` [JGIT PATCH 1/5] Sort Ref objects by OrigName and not Name Shawn O. Pearce
  0 siblings, 1 reply; 6+ messages in thread
From: Shawn O. Pearce @ 2008-12-23 22:56 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

And uh, now we can act as a true git server, providing fetch and
clone support over git:// with our own daemon, or through the use of
"git fetch --upload-pack='jgit upload-pack'".

Roughly tested by cloning the Linux kernel and WebKit (both about
500 MB packed).  Kernel takes a while to enumerate the objects,
but eh, it works.  :-)


Shawn O. Pearce (5):
  Sort Ref objects by OrigName and not Name
  Permit subclass of ObjectId (e.g. RevObject) when calling PackWriter
  Implement "jgit upload-pack" to support fetching from jgit
  Fix BaseFetchPackConnection's output of selected capabilities
  Switch local fetch connection to use our own UploadPack

 .../services/org.spearce.jgit.pgm.TextBuiltin      |    1 +
 .../src/org/spearce/jgit/pgm/UploadPack.java       |   67 +++
 .../src/org/spearce/jgit/lib/PackWriter.java       |   94 +++-
 .../src/org/spearce/jgit/lib/RefComparator.java    |    2 +-
 .../spearce/jgit/transport/BasePackConnection.java |    3 +-
 .../jgit/transport/BasePackFetchConnection.java    |    4 +
 .../jgit/transport/BasePackPushConnection.java     |    2 +-
 .../src/org/spearce/jgit/transport/Daemon.java     |   36 ++-
 .../org/spearce/jgit/transport/PacketLineOut.java  |   19 +-
 .../jgit/transport/SideBandInputStream.java        |    6 +-
 .../jgit/transport/SideBandOutputStream.java       |   93 ++++
 .../jgit/transport/SideBandProgressMonitor.java    |  150 ++++++
 .../org/spearce/jgit/transport/TransportLocal.java |   96 ++++-
 .../src/org/spearce/jgit/transport/UploadPack.java |  491 ++++++++++++++++++++
 14 files changed, 1018 insertions(+), 46 deletions(-)
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/UploadPack.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/SideBandOutputStream.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/SideBandProgressMonitor.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [JGIT PATCH 1/5] Sort Ref objects by OrigName and not Name
  2008-12-23 22:56 [JGIT PATCH 0/5] Add "jgit upload-pack" for fetch service Shawn O. Pearce
@ 2008-12-23 22:56 ` Shawn O. Pearce
  2008-12-23 22:56   ` [JGIT PATCH 2/5] Permit subclass of ObjectId (e.g. RevObject) when calling PackWriter Shawn O. Pearce
  0 siblings, 1 reply; 6+ messages in thread
From: Shawn O. Pearce @ 2008-12-23 22:56 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

This avoids sorting symrefs by their target; instead we sort the
symref by the symref's own name, thus placing "HEAD" before the
standard "refs/heads/..." namespace.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../src/org/spearce/jgit/lib/RefComparator.java    |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefComparator.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefComparator.java
index 95e3e0f..940a7ec 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefComparator.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefComparator.java
@@ -54,7 +54,7 @@
 	public static final RefComparator INSTANCE = new RefComparator();
 
 	public int compare(final Ref o1, final Ref o2) {
-		return o1.getName().compareTo(o2.getName());
+		return o1.getOrigName().compareTo(o2.getOrigName());
 	}
 
 	/**
-- 
1.6.1.rc4.301.g5497a

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [JGIT PATCH 2/5] Permit subclass of ObjectId (e.g. RevObject) when calling PackWriter
  2008-12-23 22:56 ` [JGIT PATCH 1/5] Sort Ref objects by OrigName and not Name Shawn O. Pearce
@ 2008-12-23 22:56   ` Shawn O. Pearce
  2008-12-23 22:56     ` [JGIT PATCH 3/5] Implement "jgit upload-pack" to support fetching from jgit Shawn O. Pearce
  0 siblings, 1 reply; 6+ messages in thread
From: Shawn O. Pearce @ 2008-12-23 22:56 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../src/org/spearce/jgit/lib/PackWriter.java       |    9 +++++----
 1 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java b/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java
index 32bf738..32394f2 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java
@@ -436,8 +436,9 @@ public void preparePack(final Iterator<RevObject> objectsSource)
 	 * @throws IOException
 	 *             when some I/O problem occur during reading objects.
 	 */
-	public void preparePack(final Collection<ObjectId> interestingObjects,
-			final Collection<ObjectId> uninterestingObjects,
+	public void preparePack(
+			final Collection<? extends ObjectId> interestingObjects,
+			final Collection<? extends ObjectId> uninterestingObjects,
 			final boolean thin, final boolean ignoreMissingUninteresting)
 			throws IOException {
 		ObjectWalk walker = setUpWalker(interestingObjects,
@@ -727,8 +728,8 @@ private void writeChecksum() throws IOException {
 	}
 
 	private ObjectWalk setUpWalker(
-			final Collection<ObjectId> interestingObjects,
-			final Collection<ObjectId> uninterestingObjects,
+			final Collection<? extends ObjectId> interestingObjects,
+			final Collection<? extends ObjectId> uninterestingObjects,
 			final boolean thin, final boolean ignoreMissingUninteresting)
 			throws MissingObjectException, IOException,
 			IncorrectObjectTypeException {
-- 
1.6.1.rc4.301.g5497a

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [JGIT PATCH 3/5] Implement "jgit upload-pack" to support fetching from jgit
  2008-12-23 22:56   ` [JGIT PATCH 2/5] Permit subclass of ObjectId (e.g. RevObject) when calling PackWriter Shawn O. Pearce
@ 2008-12-23 22:56     ` Shawn O. Pearce
  2008-12-23 22:56       ` [JGIT PATCH 4/5] Fix BaseFetchPackConnection's output of selected capabilities Shawn O. Pearce
  0 siblings, 1 reply; 6+ messages in thread
From: Shawn O. Pearce @ 2008-12-23 22:56 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

All current options, with the exception of shallow fetch, are
supported by this implementation.

"jgit upload-pack" is included to support remote server side
execution.  "jgit daemon" has also had the upload-pack service
added to its service table, making fetch over git:// possible.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../services/org.spearce.jgit.pgm.TextBuiltin      |    1 +
 .../src/org/spearce/jgit/pgm/UploadPack.java       |   67 +++
 .../src/org/spearce/jgit/lib/PackWriter.java       |   85 +++-
 .../jgit/transport/BasePackFetchConnection.java    |    2 +
 .../src/org/spearce/jgit/transport/Daemon.java     |   36 ++-
 .../org/spearce/jgit/transport/PacketLineOut.java  |   19 +-
 .../jgit/transport/SideBandInputStream.java        |    6 +-
 .../jgit/transport/SideBandOutputStream.java       |   93 ++++
 .../jgit/transport/SideBandProgressMonitor.java    |  150 ++++++
 .../src/org/spearce/jgit/transport/UploadPack.java |  491 ++++++++++++++++++++
 10 files changed, 915 insertions(+), 35 deletions(-)
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/UploadPack.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/SideBandOutputStream.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/SideBandProgressMonitor.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java

diff --git a/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin b/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin
index 40177f9..1ba29e6 100644
--- a/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin
+++ b/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin
@@ -16,6 +16,7 @@ org.spearce.jgit.pgm.RevList
 org.spearce.jgit.pgm.Rm
 org.spearce.jgit.pgm.ShowRev
 org.spearce.jgit.pgm.Tag
+org.spearce.jgit.pgm.UploadPack
 org.spearce.jgit.pgm.Version
 
 org.spearce.jgit.pgm.debug.MakeCacheTree
diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/UploadPack.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/UploadPack.java
new file mode 100644
index 0000000..d6f6d7c
--- /dev/null
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/UploadPack.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.pgm;
+
+import java.io.File;
+
+import org.kohsuke.args4j.Argument;
+import org.spearce.jgit.lib.Repository;
+
+@Command(common = false, usage = "Server side backend for 'jgit fetch'")
+class UploadPack extends TextBuiltin {
+	@Argument(index = 0, required = true, metaVar = "DIRECTORY", usage = "Repository to read from")
+	File gitdir;
+
+	@Override
+	protected final boolean requiresRepository() {
+		return false;
+	}
+
+	@Override
+	protected void run() throws Exception {
+		final org.spearce.jgit.transport.UploadPack rp;
+
+		if (new File(gitdir, ".git").isDirectory())
+			gitdir = new File(gitdir, ".git");
+		db = new Repository(gitdir);
+		if (!db.getObjectsDirectory().isDirectory())
+			throw die("'" + gitdir.getPath() + "' not a git repository");
+		rp = new org.spearce.jgit.transport.UploadPack(db);
+		rp.upload(System.in, System.out, System.err);
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java b/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java
index 32394f2..89460f2 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/PackWriter.java
@@ -173,7 +173,9 @@
 
 	private final Deflater deflater;
 
-	private final ProgressMonitor monitor;
+	private ProgressMonitor initMonitor;
+
+	private ProgressMonitor writeMonitor;
 
 	private final byte[] buf = new byte[16384]; // 16 KB
 
@@ -200,18 +202,41 @@
 	 * <p>
 	 * Objects for packing are specified in {@link #preparePack(Iterator)} or
 	 * {@link #preparePack(Collection, Collection, boolean, boolean)}.
-	 *
+	 * 
 	 * @param repo
 	 *            repository where objects are stored.
 	 * @param monitor
 	 *            operations progress monitor, used within
 	 *            {@link #preparePack(Iterator)},
-	 *            {@link #preparePack(Collection, Collection, boolean, boolean)},
-	 *            or {@link #writePack(OutputStream)}.
+	 *            {@link #preparePack(Collection, Collection, boolean, boolean)}
+	 *            , or {@link #writePack(OutputStream)}.
 	 */
 	public PackWriter(final Repository repo, final ProgressMonitor monitor) {
+		this(repo, monitor, monitor);
+	}
+
+	/**
+	 * Create writer for specified repository.
+	 * <p>
+	 * Objects for packing are specified in {@link #preparePack(Iterator)} or
+	 * {@link #preparePack(Collection, Collection, boolean, boolean)}.
+	 * 
+	 * @param repo
+	 *            repository where objects are stored.
+	 * @param imonitor
+	 *            operations progress monitor, used within
+	 *            {@link #preparePack(Iterator)},
+	 *            {@link #preparePack(Collection, Collection, boolean, boolean)}
+	 *            ;
+	 * @param wmonitor
+	 *            operations progress monitor, used within
+	 *            {@link #writePack(OutputStream)}.
+	 */
+	public PackWriter(final Repository repo, final ProgressMonitor imonitor,
+			final ProgressMonitor wmonitor) {
 		this.db = repo;
-		this.monitor = monitor;
+		initMonitor = imonitor;
+		writeMonitor = wmonitor;
 		this.deflater = new Deflater(db.getConfig().getCore().getCompression());
 	}
 
@@ -447,6 +472,17 @@ public void preparePack(
 	}
 
 	/**
+	 * Determine if the pack file will contain the requested object.
+	 * 
+	 * @param id
+	 *            the object to test the existence of.
+	 * @return true if the object will appear in the output pack file.
+	 */
+	public boolean willInclude(final AnyObjectId id) {
+		return objectsMap.get(id) != null;
+	}
+
+	/**
 	 * Computes SHA-1 of lexicographically sorted objects ids written in this
 	 * pack, as used to name a pack file in repository.
 	 *
@@ -529,23 +565,23 @@ public void writePack(OutputStream packStream) throws IOException {
 		countingOut = new CountingOutputStream(packStream);
 		out = new DigestOutputStream(countingOut, Constants.newMessageDigest());
 
-		monitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber());
+		writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber());
 		writeHeader();
 		writeObjects();
 		writeChecksum();
 
 		out.flush();
 		windowCursor.release();
-		monitor.endTask();
+		writeMonitor.endTask();
 	}
 
 	private void searchForReuse() throws IOException {
-		monitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber());
+		initMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber());
 		final Collection<PackedObjectLoader> reuseLoaders = new LinkedList<PackedObjectLoader>();
 
 		for (List<ObjectToPack> list : objectsLists) {
 			for (ObjectToPack otp : list) {
-				if (monitor.isCancelled())
+				if (initMonitor.isCancelled())
 					throw new IOException(
 							"Packing cancelled during objects writing");
 				reuseLoaders.clear();
@@ -557,11 +593,11 @@ private void searchForReuse() throws IOException {
 				if (reuseObjects && !otp.hasReuseLoader()) {
 					selectObjectReuseForObject(otp, reuseLoaders);
 				}
-				monitor.update(1);
+				initMonitor.update(1);
 			}
 		}
 
-		monitor.endTask();
+		initMonitor.endTask();
 	}
 
 	private void selectDeltaReuseForObject(final ObjectToPack otp,
@@ -625,7 +661,7 @@ private void writeHeader() throws IOException {
 	private void writeObjects() throws IOException {
 		for (List<ObjectToPack> list : objectsLists) {
 			for (ObjectToPack otp : list) {
-				if (monitor.isCancelled())
+				if (writeMonitor.isCancelled())
 					throw new IOException(
 							"Packing cancelled during objects writing");
 				if (!otp.isWritten())
@@ -663,7 +699,7 @@ private void writeObject(final ObjectToPack otp) throws IOException {
 		else
 			writeWholeObject(otp);
 
-		monitor.update(1);
+		writeMonitor.update(1);
 	}
 
 	private void writeWholeObject(final ObjectToPack otp) throws IOException {
@@ -760,21 +796,34 @@ private ObjectWalk setUpWalker(
 	private void findObjectsToPack(final ObjectWalk walker)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
-		monitor.beginTask(COUNTING_OBJECTS_PROGRESS, ProgressMonitor.UNKNOWN);
+		initMonitor.beginTask(COUNTING_OBJECTS_PROGRESS,
+				ProgressMonitor.UNKNOWN);
 		RevObject o;
 
 		while ((o = walker.next()) != null) {
 			addObject(o);
-			monitor.update(1);
+			initMonitor.update(1);
 		}
 		while ((o = walker.nextObject()) != null) {
 			addObject(o);
-			monitor.update(1);
+			initMonitor.update(1);
 		}
-		monitor.endTask();
+		initMonitor.endTask();
 	}
 
-	private void addObject(RevObject object)
+	/**
+	 * Include one object to the output file.
+	 * <p>
+	 * Objects are written in the order they are added. If the same object is
+	 * added twice, it may be written twice, creating a larger than necessary
+	 * file.
+	 * 
+	 * @param object
+	 *            the object to add.
+	 * @throws IncorrectObjectTypeException
+	 *             the object is an unsupported type.
+	 */
+	public void addObject(final RevObject object)
 			throws IncorrectObjectTypeException {
 		if (object.has(RevFlag.UNINTERESTING)) {
 			edgeObjects.add(object);
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java
index 2cb9b64..1fe504b 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java
@@ -100,6 +100,8 @@
 
 	static final String OPTION_SHALLOW = "shallow";
 
+	static final String OPTION_NO_PROGRESS = "no-progress";
+
 	private final RevWalk walk;
 
 	/** All commits that are immediately reachable by a local ref. */
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java
index c225740..b5097ef 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java
@@ -41,6 +41,7 @@
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InterruptedIOException;
 import java.net.InetSocketAddress;
 import java.net.ServerSocket;
@@ -96,15 +97,32 @@ public Daemon(final InetSocketAddress addr) {
 		exportBase = new ArrayList<File>();
 		processors = new ThreadGroup("Git-Daemon");
 
-		services = new DaemonService[] { new DaemonService("receive-pack",
-				"receivepack") {
-			@Override
-			protected void execute(final DaemonClient dc, final Repository db)
-					throws IOException {
-				final ReceivePack rp = new ReceivePack(db);
-				rp.receive(dc.getInputStream(), dc.getOutputStream(), null);
-			}
-		} };
+		services = new DaemonService[] {
+				new DaemonService("upload-pack", "uploadpack") {
+					{
+						setEnabled(true);
+					}
+
+					@Override
+					protected void execute(final DaemonClient dc,
+							final Repository db) throws IOException {
+						final UploadPack rp = new UploadPack(db);
+						final InputStream in = dc.getInputStream();
+						rp.upload(in, dc.getOutputStream(), null);
+					}
+				}, new DaemonService("receive-pack", "receivepack") {
+					{
+						setEnabled(false);
+					}
+
+					@Override
+					protected void execute(final DaemonClient dc,
+							final Repository db) throws IOException {
+						final ReceivePack rp = new ReceivePack(db);
+						final InputStream in = dc.getInputStream();
+						rp.receive(in, dc.getOutputStream(), null);
+					}
+				} };
 	}
 
 	/** @return the address connections are received on. */
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineOut.java b/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineOut.java
index d37b217..aae4be5 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineOut.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineOut.java
@@ -50,7 +50,7 @@
 
 	PacketLineOut(final OutputStream i) {
 		out = i;
-		lenbuffer = new byte[4];
+		lenbuffer = new byte[5];
 	}
 
 	void writeString(final String s) throws IOException {
@@ -58,12 +58,22 @@ void writeString(final String s) throws IOException {
 	}
 
 	void writePacket(final byte[] packet) throws IOException {
-		writeLength(packet.length + 4);
+		formatLength(packet.length + 4);
+		out.write(lenbuffer, 0, 4);
 		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);
+	}
+
 	void end() throws IOException {
-		writeLength(0);
+		formatLength(0);
+		out.write(lenbuffer, 0, 4);
 		flush();
 	}
 
@@ -74,7 +84,7 @@ void flush() throws IOException {
 	private static final byte[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
 			'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
 
-	private void writeLength(int w) throws IOException {
+	private void formatLength(int w) {
 		int o = 3;
 		while (o >= 0 && w != 0) {
 			lenbuffer[o--] = hexchar[w & 0xf];
@@ -82,6 +92,5 @@ private void writeLength(int w) throws IOException {
 		}
 		while (o >= 0)
 			lenbuffer[o--] = '0';
-		out.write(lenbuffer, 0, 4);
 	}
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandInputStream.java b/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandInputStream.java
index 3ec9bff..b08cf4d 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandInputStream.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandInputStream.java
@@ -66,11 +66,11 @@
  * @see PacketLineIn#sideband(ProgressMonitor)
  */
 class SideBandInputStream extends InputStream {
-	private static final int CH_DATA = 1;
+	static final int CH_DATA = 1;
 
-	private static final int CH_PROGRESS = 2;
+	static final int CH_PROGRESS = 2;
 
-	private static final int CH_ERROR = 3;
+	static final int CH_ERROR = 3;
 
 	private static Pattern P_UNBOUNDED = Pattern.compile(
 			".*?([\\w ]+): (\\d+)(, done)?.*", Pattern.DOTALL);
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandOutputStream.java b/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandOutputStream.java
new file mode 100644
index 0000000..31c9f5d
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandOutputStream.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.transport;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Multiplexes data and progress messages
+ * <p>
+ * To correctly use this class you must wrap it in a BufferedOutputStream with a
+ * buffer size no larger than either {@link #SMALL_BUF} or {@link #MAX_BUF},
+ * minus {@link #HDR_SIZE}.
+ */
+class SideBandOutputStream extends OutputStream {
+	static final int CH_DATA = SideBandInputStream.CH_DATA;
+
+	static final int CH_PROGRESS = SideBandInputStream.CH_PROGRESS;
+
+	static final int CH_ERROR = SideBandInputStream.CH_ERROR;
+
+	static final int SMALL_BUF = 1000;
+
+	static final int MAX_BUF = 65520;
+
+	static final int HDR_SIZE = 5;
+
+	private final int channel;
+
+	private final PacketLineOut pckOut;
+
+	private byte[] singleByteBuffer;
+
+	SideBandOutputStream(final int chan, final PacketLineOut out) {
+		channel = chan;
+		pckOut = out;
+	}
+
+	@Override
+	public void flush() throws IOException {
+		if (channel != CH_DATA)
+			pckOut.flush();
+	}
+
+	@Override
+	public void write(final byte[] b, final int off, final int len)
+			throws IOException {
+		pckOut.writeChannelPacket(channel, b, off, len);
+	}
+
+	@Override
+	public void write(final int b) throws IOException {
+		if (singleByteBuffer == null)
+			singleByteBuffer = new byte[1];
+		singleByteBuffer[0] = (byte) b;
+		write(singleByteBuffer);
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandProgressMonitor.java b/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandProgressMonitor.java
new file mode 100644
index 0000000..5cda7c5
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandProgressMonitor.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.transport;
+
+import java.io.BufferedOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.ProgressMonitor;
+
+/** Write progress messages out to the sideband channel. */
+class SideBandProgressMonitor implements ProgressMonitor {
+	private PrintWriter out;
+
+	private boolean output;
+
+	private long taskBeganAt;
+
+	private long lastOutput;
+
+	private String msg;
+
+	private int lastWorked;
+
+	private int totalWork;
+
+	SideBandProgressMonitor(final PacketLineOut pckOut) {
+		final int bufsz = SideBandOutputStream.SMALL_BUF
+				- 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) {
+		// Ignore the number of tasks.
+		taskBeganAt = System.currentTimeMillis();
+		lastOutput = taskBeganAt;
+	}
+
+	public void beginTask(final String title, final int total) {
+		endTask();
+		msg = title;
+		lastWorked = 0;
+		totalWork = total;
+	}
+
+	public void update(final int completed) {
+		if (msg == null)
+			return;
+
+		final int cmp = lastWorked + completed;
+		final long now = System.currentTimeMillis();
+		if (!output && now - taskBeganAt < 500)
+			return;
+		if (totalWork == UNKNOWN) {
+			if (now - lastOutput >= 500) {
+				display(cmp, null);
+				lastOutput = now;
+			}
+		} else {
+			if ((cmp * 100 / totalWork) != (lastWorked * 100) / totalWork
+					|| now - lastOutput >= 500) {
+				display(cmp, null);
+				lastOutput = now;
+			}
+		}
+		lastWorked = cmp;
+		output = true;
+	}
+
+	private void display(final int cmp, final String eol) {
+		final StringBuilder m = new StringBuilder();
+		m.append(msg);
+		m.append(": ");
+
+		if (totalWork == UNKNOWN) {
+			m.append(cmp);
+		} else {
+			final int pcnt = (cmp * 100 / totalWork);
+			if (pcnt < 100)
+				m.append(' ');
+			if (pcnt < 10)
+				m.append(' ');
+			m.append(pcnt);
+			m.append("% (");
+			m.append(cmp);
+			m.append("/");
+			m.append(totalWork);
+			m.append(")");
+		}
+		if (eol != null)
+			m.append(eol);
+		else
+			m.append("   \r");
+		out.print(m);
+		out.flush();
+	}
+
+	public boolean isCancelled() {
+		return false;
+	}
+
+	public void endTask() {
+		if (output) {
+			if (totalWork == UNKNOWN)
+				display(lastWorked, ", done\n");
+			else
+				display(totalWork, "\n");
+		}
+		output = false;
+		msg = null;
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java
new file mode 100644
index 0000000..4401951
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.transport;
+
+import java.io.BufferedOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.spearce.jgit.errors.PackProtocolException;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.NullProgressMonitor;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.PackWriter;
+import org.spearce.jgit.lib.ProgressMonitor;
+import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.lib.RefComparator;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.revwalk.RevCommit;
+import org.spearce.jgit.revwalk.RevFlag;
+import org.spearce.jgit.revwalk.RevFlagSet;
+import org.spearce.jgit.revwalk.RevObject;
+import org.spearce.jgit.revwalk.RevTag;
+import org.spearce.jgit.revwalk.RevWalk;
+
+/**
+ * Implements the server side of a fetch connection, transmitting objects.
+ */
+public class UploadPack {
+	static final String OPTION_INCLUDE_TAG = BasePackFetchConnection.OPTION_INCLUDE_TAG;
+
+	static final String OPTION_MULTI_ACK = BasePackFetchConnection.OPTION_MULTI_ACK;
+
+	static final String OPTION_THIN_PACK = BasePackFetchConnection.OPTION_THIN_PACK;
+
+	static final String OPTION_SIDE_BAND = BasePackFetchConnection.OPTION_SIDE_BAND;
+
+	static final String OPTION_SIDE_BAND_64K = BasePackFetchConnection.OPTION_SIDE_BAND_64K;
+
+	static final String OPTION_OFS_DELTA = BasePackFetchConnection.OPTION_OFS_DELTA;
+
+	static final String OPTION_NO_PROGRESS = BasePackFetchConnection.OPTION_NO_PROGRESS;
+
+	/** Database we read the objects from. */
+	private final Repository db;
+
+	/** Revision traversal support over {@link #db}. */
+	private final RevWalk walk;
+
+	private InputStream rawIn;
+
+	private OutputStream rawOut;
+
+	private PacketLineIn pckIn;
+
+	private PacketLineOut pckOut;
+
+	/** The refs we advertised as existing at the start of the connection. */
+	private Map<String, Ref> refs;
+
+	/** Capabilities requested by the client. */
+	private final Set<String> options = new HashSet<String>();
+
+	/** Objects the client wants to obtain. */
+	private final List<RevObject> wantAll = new ArrayList<RevObject>();
+
+	/** Objects the client wants to obtain. */
+	private final List<RevCommit> wantCommits = new ArrayList<RevCommit>();
+
+	/** Objects on both sides, these don't have to be sent. */
+	private final List<RevObject> commonBase = new ArrayList<RevObject>();
+
+	/** Marked on objects we sent in our advertisement list. */
+	private final RevFlag ADVERTISED;
+
+	/** Marked on objects the client has asked us to give them. */
+	private final RevFlag WANT;
+
+	/** Marked on objects both we and the client have. */
+	private final RevFlag PEER_HAS;
+
+	/** Marked on objects in {@link #commonBase}. */
+	private final RevFlag COMMON;
+
+	private final RevFlagSet SAVE;
+
+	private boolean multiAck;
+
+	/**
+	 * Create a new pack upload for an open repository.
+	 * 
+	 * @param copyFrom
+	 *            the source repository.
+	 */
+	public UploadPack(final Repository copyFrom) {
+		db = copyFrom;
+		walk = new RevWalk(db);
+
+		ADVERTISED = walk.newFlag("ADVERTISED");
+		WANT = walk.newFlag("WANT");
+		PEER_HAS = walk.newFlag("PEER_HAS");
+		COMMON = walk.newFlag("COMMON");
+		walk.carry(PEER_HAS);
+
+		SAVE = new RevFlagSet();
+		SAVE.add(ADVERTISED);
+		SAVE.add(WANT);
+		SAVE.add(PEER_HAS);
+	}
+
+	/** @return the repository this receive completes into. */
+	public final Repository getRepository() {
+		return db;
+	}
+
+	/** @return the RevWalk instance used by this connection. */
+	public final RevWalk getRevWalk() {
+		return walk;
+	}
+
+	/**
+	 * Execute the upload task on the socket.
+	 * 
+	 * @param input
+	 *            raw input to read client commands from. Caller must ensure the
+	 *            input is buffered, otherwise read performance may suffer.
+	 * @param output
+	 *            response back to the Git network client, to write the pack
+	 *            data onto. Caller must ensure the output is buffered,
+	 *            otherwise write performance may suffer.
+	 * @param messages
+	 *            secondary "notice" channel to send additional messages out
+	 *            through. When run over SSH this should be tied back to the
+	 *            standard error channel of the command execution. For most
+	 *            other network connections this should be null.
+	 * @throws IOException
+	 */
+	public void upload(final InputStream input, final OutputStream output,
+			final OutputStream messages) throws IOException {
+		rawIn = input;
+		rawOut = output;
+
+		pckIn = new PacketLineIn(rawIn);
+		pckOut = new PacketLineOut(rawOut);
+		service();
+	}
+
+	private void service() throws IOException {
+		sendAdvertisedRefs();
+		recvWants();
+		if (wantAll.isEmpty())
+			return;
+		multiAck = options.contains(OPTION_MULTI_ACK);
+		negotiate();
+		sendPack();
+	}
+
+	private void sendAdvertisedRefs() throws IOException {
+		refs = db.getAllRefs();
+
+		final StringBuilder m = new StringBuilder(100);
+		final char[] idtmp = new char[2 * Constants.OBJECT_ID_LENGTH];
+		final Iterator<Ref> i = RefComparator.sort(refs.values()).iterator();
+		if (i.hasNext()) {
+			final Ref r = i.next();
+			final RevObject o = safeParseAny(r.getObjectId());
+			if (o != null) {
+				advertise(m, idtmp, o, r.getOrigName());
+				m.append('\0');
+				m.append(' ');
+				m.append(OPTION_INCLUDE_TAG);
+				m.append(' ');
+				m.append(OPTION_MULTI_ACK);
+				m.append(' ');
+				m.append(OPTION_OFS_DELTA);
+				m.append(' ');
+				m.append(OPTION_SIDE_BAND);
+				m.append(' ');
+				m.append(OPTION_SIDE_BAND_64K);
+				m.append(' ');
+				m.append(OPTION_THIN_PACK);
+				m.append(' ');
+				m.append(OPTION_NO_PROGRESS);
+				m.append(' ');
+				writeAdvertisedRef(m);
+				if (o instanceof RevTag)
+					writeAdvertisedTag(m, idtmp, o, r.getName());
+			}
+		}
+		while (i.hasNext()) {
+			final Ref r = i.next();
+			final RevObject o = safeParseAny(r.getObjectId());
+			if (o != null) {
+				advertise(m, idtmp, o, r.getOrigName());
+				writeAdvertisedRef(m);
+				if (o instanceof RevTag)
+					writeAdvertisedTag(m, idtmp, o, r.getName());
+			}
+		}
+		pckOut.end();
+	}
+
+	private RevObject safeParseAny(final ObjectId id) {
+		try {
+			return walk.parseAny(id);
+		} catch (IOException e) {
+			return null;
+		}
+	}
+
+	private void advertise(final StringBuilder m, final char[] idtmp,
+			final RevObject o, final String name) {
+		o.add(ADVERTISED);
+		m.setLength(0);
+		o.getId().copyTo(idtmp, m);
+		m.append(' ');
+		m.append(name);
+	}
+
+	private void writeAdvertisedRef(final StringBuilder m) throws IOException {
+		m.append('\n');
+		pckOut.writeString(m.toString());
+	}
+
+	private void writeAdvertisedTag(final StringBuilder m, final char[] idtmp,
+			final RevObject tag, final String name) throws IOException {
+		RevObject o = tag;
+		while (o instanceof RevTag) {
+			// Fully unwrap here so later on we have these already parsed.
+			try {
+				walk.parse(((RevTag) o).getObject());
+			} catch (IOException err) {
+				return;
+			}
+			o = ((RevTag) o).getObject();
+			o.add(ADVERTISED);
+		}
+		advertise(m, idtmp, ((RevTag) tag).getObject(), name + "^{}");
+		writeAdvertisedRef(m);
+	}
+
+	private void recvWants() throws IOException {
+		boolean isFirst = true;
+		for (;; isFirst = false) {
+			String line;
+			try {
+				line = pckIn.readString();
+			} catch (EOFException eof) {
+				if (isFirst)
+					break;
+				throw eof;
+			}
+
+			if (line.length() == 0)
+				break;
+			if (!line.startsWith("want ") || line.length() < 45)
+				throw new PackProtocolException("expected want; got " + line);
+
+			if (isFirst) {
+				final int sp = line.indexOf(' ', 45);
+				if (sp >= 0) {
+					for (String c : line.substring(sp + 1).split(" "))
+						options.add(c);
+					line = line.substring(0, sp);
+				}
+			}
+
+			final ObjectId id = ObjectId.fromString(line.substring(5));
+			final RevObject o;
+			try {
+				o = walk.parseAny(id);
+			} catch (IOException e) {
+				throw new PackProtocolException(id.name() + " not valid", e);
+			}
+			if (!o.has(ADVERTISED))
+				throw new PackProtocolException(id.name() + " not valid");
+			want(o);
+		}
+	}
+
+	private void want(RevObject o) {
+		if (!o.has(WANT)) {
+			o.add(WANT);
+			wantAll.add(o);
+
+			if (o instanceof RevCommit)
+				wantCommits.add((RevCommit) o);
+
+			else if (o instanceof RevTag) {
+				do {
+					o = ((RevTag) o).getObject();
+				} while (o instanceof RevTag);
+				if (o instanceof RevCommit)
+					want(o);
+			}
+		}
+	}
+
+	private void negotiate() throws IOException {
+		ObjectId last = ObjectId.zeroId();
+		for (;;) {
+			String line;
+			try {
+				line = pckIn.readString();
+			} catch (EOFException eof) {
+				throw eof;
+			}
+
+			if (line.length() == 0) {
+				if (commonBase.isEmpty() || multiAck)
+					pckOut.writeString("NAK\n");
+
+			} else if (line.startsWith("have ") && line.length() == 45) {
+				final ObjectId id = ObjectId.fromString(line.substring(5));
+				if (matchHave(id)) {
+					// Both sides have the same object; let the client know.
+					//
+					if (multiAck) {
+						last = id;
+						pckOut.writeString("ACK " + id.name() + " continue\n");
+					} else if (commonBase.size() == 1)
+						pckOut.writeString("ACK " + id.name() + "\n");
+				} else {
+					// They have this object; we don't.
+					//
+					if (multiAck && okToGiveUp())
+						pckOut.writeString("ACK " + id.name() + " continue\n");
+				}
+
+			} else if (line.equals("done")) {
+				if (commonBase.isEmpty())
+					pckOut.writeString("NAK\n");
+
+				else if (multiAck)
+					pckOut.writeString("ACK " + last.name() + "\n");
+				break;
+
+			} else {
+				throw new PackProtocolException("expected have; got " + line);
+			}
+		}
+	}
+
+	private boolean matchHave(final ObjectId id) {
+		final RevObject o;
+		try {
+			o = walk.parseAny(id);
+		} catch (IOException err) {
+			return false;
+		}
+
+		if (!o.has(PEER_HAS)) {
+			o.add(PEER_HAS);
+			if (o instanceof RevCommit)
+				((RevCommit) o).carry(PEER_HAS);
+			if (!o.has(COMMON)) {
+				o.add(COMMON);
+				commonBase.add(o);
+			}
+		}
+		return true;
+	}
+
+	private boolean okToGiveUp() throws PackProtocolException {
+		if (commonBase.isEmpty())
+			return false;
+
+		try {
+			for (final Iterator<RevCommit> i = wantCommits.iterator(); i
+					.hasNext();) {
+				final RevCommit want = i.next();
+				if (wantSatisfied(want))
+					i.remove();
+			}
+		} catch (IOException e) {
+			throw new PackProtocolException("internal revision error", e);
+		}
+		return wantCommits.isEmpty();
+	}
+
+	private boolean wantSatisfied(final RevCommit want) throws IOException {
+		walk.resetRetain(SAVE);
+		walk.markStart(want);
+		for (;;) {
+			final RevCommit c = walk.next();
+			if (c == null)
+				break;
+			if (c.has(PEER_HAS)) {
+				if (!c.has(COMMON)) {
+					c.add(COMMON);
+					commonBase.add(c);
+				}
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private void sendPack() throws IOException {
+		final boolean thin = options.contains(OPTION_THIN_PACK);
+		final boolean progress = !options.contains(OPTION_NO_PROGRESS);
+		final boolean sideband = options.contains(OPTION_SIDE_BAND)
+				|| options.contains(OPTION_SIDE_BAND_64K);
+
+		ProgressMonitor pm = NullProgressMonitor.INSTANCE;
+		OutputStream packOut = rawOut;
+
+		if (sideband) {
+			int bufsz = SideBandOutputStream.SMALL_BUF;
+			if (options.contains(OPTION_SIDE_BAND_64K))
+				bufsz = SideBandOutputStream.MAX_BUF;
+			bufsz -= SideBandOutputStream.HDR_SIZE;
+
+			packOut = new BufferedOutputStream(new SideBandOutputStream(
+					SideBandOutputStream.CH_DATA, pckOut), bufsz);
+
+			if (progress)
+				pm = new SideBandProgressMonitor(pckOut);
+		}
+
+		final PackWriter pw;
+		pw = new PackWriter(db, pm, NullProgressMonitor.INSTANCE);
+		pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
+		pw.preparePack(wantAll, commonBase, thin, true);
+		if (options.contains(OPTION_INCLUDE_TAG)) {
+			for (final Ref r : refs.values()) {
+				final RevObject o;
+				try {
+					o = walk.parseAny(r.getObjectId());
+				} catch (IOException e) {
+					continue;
+				}
+				if (o.has(WANT) || !(o instanceof RevTag))
+					continue;
+				final RevTag t = (RevTag) o;
+				if (!pw.willInclude(t) && pw.willInclude(t.getObject()))
+					pw.addObject(t);
+			}
+		}
+		pw.writePack(packOut);
+
+		if (sideband) {
+			packOut.flush();
+			pckOut.end();
+		} else {
+			rawOut.flush();
+		}
+	}
+}
-- 
1.6.1.rc4.301.g5497a

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [JGIT PATCH 4/5] Fix BaseFetchPackConnection's output of selected capabilities
  2008-12-23 22:56     ` [JGIT PATCH 3/5] Implement "jgit upload-pack" to support fetching from jgit Shawn O. Pearce
@ 2008-12-23 22:56       ` Shawn O. Pearce
  2008-12-23 22:56         ` [JGIT PATCH 5/5] Switch local fetch connection to use our own UploadPack Shawn O. Pearce
  0 siblings, 1 reply; 6+ messages in thread
From: Shawn O. Pearce @ 2008-12-23 22:56 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

When we output the first "have ..." line for an upload-pack server
process we need to dump "have $id $cap1 $cap2 ..", where there is
a space between the SHA-1 $id and the first capability name $cap1.
If we don't dump that space we run into errors with our own version
of upload-pack not being able to parse the SHA-1 out of the line,
as the line was split incorrectly.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../spearce/jgit/transport/BasePackConnection.java |    3 +--
 .../jgit/transport/BasePackPushConnection.java     |    2 +-
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackConnection.java
index e9df30e..c9232ce 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackConnection.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackConnection.java
@@ -207,8 +207,7 @@ protected boolean isCapableOf(final String option) {
 	protected boolean wantCapability(final StringBuilder b, final String option) {
 		if (!isCapableOf(option))
 			return false;
-		if (b.length() > 0)
-			b.append(' ');
+		b.append(' ');
 		b.append(option);
 		return true;
 	}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java
index 17f6915..a078d7e 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java
@@ -181,7 +181,7 @@ private String enableCapabilities() {
 		capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS);
 		capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
 		if (line.length() > 0)
-			line.insert(0, '\0');
+			line.setCharAt(0, '\0');
 		return line.toString();
 	}
 
-- 
1.6.1.rc4.301.g5497a

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [JGIT PATCH 5/5] Switch local fetch connection to use our own UploadPack
  2008-12-23 22:56       ` [JGIT PATCH 4/5] Fix BaseFetchPackConnection's output of selected capabilities Shawn O. Pearce
@ 2008-12-23 22:56         ` Shawn O. Pearce
  0 siblings, 0 replies; 6+ messages in thread
From: Shawn O. Pearce @ 2008-12-23 22:56 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

Just like with push, we now use our own version of upload pack to
perform a fetch from a local repository.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../jgit/transport/BasePackFetchConnection.java    |    2 +
 .../org/spearce/jgit/transport/TransportLocal.java |   96 +++++++++++++++++++-
 2 files changed, 95 insertions(+), 3 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java
index 1fe504b..19ac161 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java
@@ -86,6 +86,8 @@
 	 */
 	private static final int MAX_HAVES = 256;
 
+	protected static final int MAX_CLIENT_BUFFER = MAX_HAVES * 46 + 1024;
+
 	static final String OPTION_INCLUDE_TAG = "include-tag";
 
 	static final String OPTION_MULTI_ACK = "multi_ack";
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java
index b5dd5fc..17d95c2 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java
@@ -86,7 +86,10 @@ TransportLocal(final Repository local, final URIish uri) {
 
 	@Override
 	public FetchConnection openFetch() throws TransportException {
-		return new LocalFetchConnection();
+		final String up = getOptionUploadPack();
+		if ("git-upload-pack".equals(up) || "git upload-pack".equals(up))
+			return new InternalLocalFetchConnection();
+		return new ForkLocalFetchConnection();
 	}
 
 	@Override
@@ -130,10 +133,97 @@ protected Process startProcessWithErrStream(final String cmd)
 		}
 	}
 
-	class LocalFetchConnection extends BasePackFetchConnection {
+	class InternalLocalFetchConnection extends BasePackFetchConnection {
+		private Thread worker;
+
+		InternalLocalFetchConnection() throws TransportException {
+			super(TransportLocal.this);
+
+			final Repository dst;
+			try {
+				dst = new Repository(remoteGitDir);
+			} catch (IOException err) {
+				throw new TransportException(uri, "not a git directory");
+			}
+
+			final PipedInputStream in_r;
+			final PipedOutputStream in_w;
+
+			final PipedInputStream out_r;
+			final PipedOutputStream out_w;
+			try {
+				in_r = new PipedInputStream();
+				in_w = new PipedOutputStream(in_r);
+
+				out_r = new PipedInputStream() {
+					// The client (BasePackFetchConnection) can write
+					// a huge burst before it reads again. We need to
+					// force the buffer to be big enough, otherwise it
+					// will deadlock both threads.
+					{
+						buffer = new byte[MAX_CLIENT_BUFFER];
+					}
+				};
+				out_w = new PipedOutputStream(out_r);
+			} catch (IOException err) {
+				dst.close();
+				throw new TransportException(uri, "cannot connect pipes", err);
+			}
+
+			worker = new Thread("JGit-Upload-Pack") {
+				public void run() {
+					try {
+						final UploadPack rp = new UploadPack(dst);
+						rp.upload(out_r, in_w, null);
+					} catch (IOException err) {
+						// Client side of the pipes should report the problem.
+						err.printStackTrace();
+					} catch (RuntimeException err) {
+						// Clients side will notice we went away, and report.
+						err.printStackTrace();
+					} finally {
+						try {
+							out_r.close();
+						} catch (IOException e2) {
+							// Ignore close failure, we probably crashed above.
+						}
+
+						try {
+							in_w.close();
+						} catch (IOException e2) {
+							// Ignore close failure, we probably crashed above.
+						}
+
+						dst.close();
+					}
+				}
+			};
+			worker.start();
+
+			init(in_r, out_w);
+			readAdvertisedRefs();
+		}
+
+		@Override
+		public void close() {
+			super.close();
+
+			if (worker != null) {
+				try {
+					worker.join();
+				} catch (InterruptedException ie) {
+					// Stop waiting and return anyway.
+				} finally {
+					worker = null;
+				}
+			}
+		}
+	}
+
+	class ForkLocalFetchConnection extends BasePackFetchConnection {
 		private Process uploadPack;
 
-		LocalFetchConnection() throws TransportException {
+		ForkLocalFetchConnection() throws TransportException {
 			super(TransportLocal.this);
 			uploadPack = startProcessWithErrStream(getOptionUploadPack());
 			init(uploadPack.getInputStream(), uploadPack.getOutputStream());
-- 
1.6.1.rc4.301.g5497a

^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2008-12-23 22:58 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-12-23 22:56 [JGIT PATCH 0/5] Add "jgit upload-pack" for fetch service Shawn O. Pearce
2008-12-23 22:56 ` [JGIT PATCH 1/5] Sort Ref objects by OrigName and not Name Shawn O. Pearce
2008-12-23 22:56   ` [JGIT PATCH 2/5] Permit subclass of ObjectId (e.g. RevObject) when calling PackWriter Shawn O. Pearce
2008-12-23 22:56     ` [JGIT PATCH 3/5] Implement "jgit upload-pack" to support fetching from jgit Shawn O. Pearce
2008-12-23 22:56       ` [JGIT PATCH 4/5] Fix BaseFetchPackConnection's output of selected capabilities Shawn O. Pearce
2008-12-23 22:56         ` [JGIT PATCH 5/5] Switch local fetch connection to use our own UploadPack Shawn O. Pearce

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).