From 1f2022e3a7e9482caa4823e031504b6bbe8246af Mon Sep 17 00:00:00 2001 From: Dave Borowitz Date: Wed, 8 Feb 2012 14:30:04 -0800 Subject: Modify refs in UploadPack/ReceivePack using a hook interface This is intended to replace the RefFilter interface (but does not yet, for backwards compatibility). That interface required lots of extra scanning and copying in filter cases such as only advertising a subtree of the refs directory. Instead, provide a hook that can be executed right before ref advertisement, using the public methods on UploadPack/ReceivePack to explicitly set the map of advertised refs. Change-Id: I0067019a191c8148af2cfb71a675f2258c5af0ca --- .../jgit/http/server/SmartServiceInfoRefs.java | 4 +- .../jgit/http/server/UploadPackServlet.java | 4 +- .../ReceivePackAdvertiseRefsHookTest.java | 575 +++++++++++++++++++++ .../jgit/transport/ReceivePackRefFilterTest.java | 574 -------------------- .../jgit/transport/AbstractAdvertiseRefsHook.java | 105 ++++ .../eclipse/jgit/transport/AdvertiseRefsHook.java | 87 ++++ .../org/eclipse/jgit/transport/PreUploadHook.java | 30 +- .../eclipse/jgit/transport/PreUploadHookChain.java | 8 +- .../org/eclipse/jgit/transport/ReceivePack.java | 127 ++++- .../transport/ServiceMayNotContinueException.java | 77 +++ .../src/org/eclipse/jgit/transport/UploadPack.java | 80 ++- .../UploadPackMayNotContinueException.java | 23 +- 12 files changed, 1040 insertions(+), 654 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java index c0f1f6335b..907b328db9 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java @@ -65,7 +65,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.PacketLineOut; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; -import org.eclipse.jgit.transport.UploadPackMayNotContinueException; +import org.eclipse.jgit.transport.ServiceMayNotContinueException; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; @@ -137,7 +137,7 @@ abstract class SmartServiceInfoRefs implements Filter { } catch (ServiceNotEnabledException e) { sendError(req, res, SC_FORBIDDEN); - } catch (UploadPackMayNotContinueException e) { + } catch (ServiceMayNotContinueException e) { if (e.isOutput()) buf.close(); else diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java index 15ef2c7eac..36d4588f13 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java @@ -73,7 +73,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.transport.UploadPack; import org.eclipse.jgit.transport.UploadPackInternalServerErrorException; -import org.eclipse.jgit.transport.UploadPackMayNotContinueException; +import org.eclipse.jgit.transport.ServiceMayNotContinueException; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import org.eclipse.jgit.transport.resolver.UploadPackFactory; @@ -176,7 +176,7 @@ class UploadPackServlet extends HttpServlet { up.upload(getInputStream(req), out, null); out.close(); - } catch (UploadPackMayNotContinueException e) { + } catch (ServiceMayNotContinueException e) { if (e.isOutput()) { consumeRequestBody(req); out.close(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java new file mode 100644 index 0000000000..5f17394e2e --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java @@ -0,0 +1,575 @@ +/* + * 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.transport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.MessageDigest; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.Deflater; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.UnpackException; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +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.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.ObjectDirectory; +import org.eclipse.jgit.storage.pack.BinaryDelta; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.TemporaryBuffer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ReceivePackAdvertiseRefsHookTest extends LocalDiskRepositoryTestCase { + private static final NullProgressMonitor PM = NullProgressMonitor.INSTANCE; + + private static final String R_MASTER = Constants.R_HEADS + Constants.MASTER; + + private static final String R_PRIVATE = Constants.R_HEADS + "private"; + + private Repository src; + + private Repository dst; + + private RevCommit A, B, P; + + private RevBlob a, b; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + src = createBareRepository(); + dst = createBareRepository(); + + // Fill dst with a some common history. + // + TestRepository d = new TestRepository(dst); + a = d.blob("a"); + A = d.commit(d.tree(d.file("a", a))); + B = d.commit().parent(A).create(); + d.update(R_MASTER, B); + + // Clone from dst into src + // + Transport t = Transport.open(src, uriOf(dst)); + try { + t.fetch(PM, Collections.singleton(new RefSpec("+refs/*:refs/*"))); + assertEquals(B, src.resolve(R_MASTER)); + } finally { + t.close(); + } + + // Now put private stuff into dst. + // + b = d.blob("b"); + P = d.commit(d.tree(d.file("b", b)), A); + d.update(R_PRIVATE, P); + } + + @Override + @After + public void tearDown() throws Exception { + if (src != null) + src.close(); + if (dst != null) + dst.close(); + super.tearDown(); + } + + @Test + public void testFilterHidesPrivate() throws Exception { + Map refs; + TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) { + @Override + ReceivePack createReceivePack(final Repository db) { + db.close(); + dst.incrementOpen(); + + final ReceivePack rp = super.createReceivePack(dst); + rp.setAdvertiseRefsHook(new HidePrivateHook()); + return rp; + } + }; + try { + PushConnection c = t.openPush(); + try { + refs = c.getRefsMap(); + } finally { + c.close(); + } + } finally { + t.close(); + } + + assertNotNull(refs); + assertNull("no private", refs.get(R_PRIVATE)); + assertNull("no HEAD", refs.get(Constants.HEAD)); + assertEquals(1, refs.size()); + + Ref master = refs.get(R_MASTER); + assertNotNull("has master", master); + assertEquals(B, master.getObjectId()); + } + + @Test + public void testSuccess() throws Exception { + // Manually force a delta of an object so we reuse it later. + // + TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); + + packHeader(pack, 2); + pack.write((Constants.OBJ_BLOB) << 4 | 1); + deflate(pack, new byte[] { 'a' }); + + pack.write((Constants.OBJ_REF_DELTA) << 4 | 4); + a.copyRawTo(pack); + deflate(pack, new byte[] { 0x1, 0x1, 0x1, 'b' }); + + digest(pack); + openPack(pack); + + // Verify the only storage of b is our packed delta above. + // + ObjectDirectory od = (ObjectDirectory) src.getObjectDatabase(); + assertTrue("has b", src.hasObject(b)); + assertFalse("b not loose", od.fileFor(b).exists()); + + // Now use b but in a different commit than what is hidden. + // + TestRepository s = new TestRepository(src); + RevCommit N = s.commit().parent(B).add("q", b).create(); + s.update(R_MASTER, N); + + // Push this new content to the remote, doing strict validation. + // + TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) { + @Override + ReceivePack createReceivePack(final Repository db) { + db.close(); + dst.incrementOpen(); + + final ReceivePack rp = super.createReceivePack(dst); + rp.setCheckReceivedObjects(true); + rp.setCheckReferencedObjectsAreReachable(true); + rp.setAdvertiseRefsHook(new HidePrivateHook()); + return rp; + } + }; + RemoteRefUpdate u = new RemoteRefUpdate( // + src, // + R_MASTER, // src name + R_MASTER, // dst name + false, // do not force update + null, // local tracking branch + null // expected id + ); + PushResult r; + try { + t.setPushThin(true); + r = t.push(PM, Collections.singleton(u)); + } finally { + t.close(); + } + + assertNotNull("have result", r); + assertNull("private not advertised", r.getAdvertisedRef(R_PRIVATE)); + assertSame("master updated", RemoteRefUpdate.Status.OK, u.getStatus()); + assertEquals(N, dst.resolve(R_MASTER)); + } + + @Test + public void testCreateBranchAtHiddenCommitFails() throws Exception { + final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64); + packHeader(pack, 0); + digest(pack); + + final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256); + final PacketLineOut inPckLine = new PacketLineOut(inBuf); + inPckLine.writeString(ObjectId.zeroId().name() + ' ' + P.name() + ' ' + + "refs/heads/s" + '\0' + + BasePackPushConnection.CAPABILITY_REPORT_STATUS); + inPckLine.end(); + pack.writeTo(inBuf, PM); + + final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); + final ReceivePack rp = new ReceivePack(dst); + rp.setCheckReceivedObjects(true); + rp.setCheckReferencedObjectsAreReachable(true); + rp.setAdvertiseRefsHook(new HidePrivateHook()); + try { + receive(rp, inBuf, outBuf); + fail("Expected UnpackException"); + } catch (UnpackException failed) { + Throwable err = failed.getCause(); + assertTrue(err instanceof MissingObjectException); + MissingObjectException moe = (MissingObjectException) err; + assertEquals(P, moe.getObjectId()); + } + + final PacketLineIn r = asPacketLineIn(outBuf); + String master = r.readString(); + int nul = master.indexOf('\0'); + assertTrue("has capability list", nul > 0); + assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); + assertSame(PacketLineIn.END, r.readString()); + + assertEquals("unpack error Missing commit " + P.name(), r.readString()); + assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); + assertSame(PacketLineIn.END, r.readString()); + } + + private void receive(final ReceivePack rp, + final TemporaryBuffer.Heap inBuf, final TemporaryBuffer.Heap outBuf) + throws IOException { + rp.receive(new ByteArrayInputStream(inBuf.toByteArray()), outBuf, null); + } + + @Test + public void testUsingHiddenDeltaBaseFails() throws Exception { + byte[] delta = { 0x1, 0x1, 0x1, 'c' }; + TestRepository s = new TestRepository(src); + RevCommit N = s.commit().parent(B).add("q", + s.blob(BinaryDelta.apply(dst.open(b).getCachedBytes(), delta))) + .create(); + + final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); + packHeader(pack, 3); + copy(pack, src.open(N)); + copy(pack, src.open(s.parseBody(N).getTree())); + pack.write((Constants.OBJ_REF_DELTA) << 4 | 4); + b.copyRawTo(pack); + deflate(pack, delta); + digest(pack); + + final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); + final PacketLineOut inPckLine = new PacketLineOut(inBuf); + inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' + + "refs/heads/s" + '\0' + + BasePackPushConnection.CAPABILITY_REPORT_STATUS); + inPckLine.end(); + pack.writeTo(inBuf, PM); + + final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); + final ReceivePack rp = new ReceivePack(dst); + rp.setCheckReceivedObjects(true); + rp.setCheckReferencedObjectsAreReachable(true); + rp.setAdvertiseRefsHook(new HidePrivateHook()); + try { + receive(rp, inBuf, outBuf); + fail("Expected UnpackException"); + } catch (UnpackException failed) { + Throwable err = failed.getCause(); + assertTrue(err instanceof MissingObjectException); + MissingObjectException moe = (MissingObjectException) err; + assertEquals(b, moe.getObjectId()); + } + + final PacketLineIn r = asPacketLineIn(outBuf); + String master = r.readString(); + int nul = master.indexOf('\0'); + assertTrue("has capability list", nul > 0); + assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); + assertSame(PacketLineIn.END, r.readString()); + + assertEquals("unpack error Missing blob " + b.name(), r.readString()); + assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); + assertSame(PacketLineIn.END, r.readString()); + } + + @Test + public void testUsingHiddenCommonBlobFails() throws Exception { + // Try to use the 'b' blob that is hidden. + // + TestRepository s = new TestRepository(src); + RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create(); + + // But don't include it in the pack. + // + final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); + packHeader(pack, 2); + copy(pack, src.open(N)); + copy(pack,src.open(s.parseBody(N).getTree())); + digest(pack); + + final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); + final PacketLineOut inPckLine = new PacketLineOut(inBuf); + inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' + + "refs/heads/s" + '\0' + + BasePackPushConnection.CAPABILITY_REPORT_STATUS); + inPckLine.end(); + pack.writeTo(inBuf, PM); + + final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); + final ReceivePack rp = new ReceivePack(dst); + rp.setCheckReceivedObjects(true); + rp.setCheckReferencedObjectsAreReachable(true); + rp.setAdvertiseRefsHook(new HidePrivateHook()); + try { + receive(rp, inBuf, outBuf); + fail("Expected UnpackException"); + } catch (UnpackException failed) { + Throwable err = failed.getCause(); + assertTrue(err instanceof MissingObjectException); + MissingObjectException moe = (MissingObjectException) err; + assertEquals(b, moe.getObjectId()); + } + + final PacketLineIn r = asPacketLineIn(outBuf); + String master = r.readString(); + int nul = master.indexOf('\0'); + assertTrue("has capability list", nul > 0); + assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); + assertSame(PacketLineIn.END, r.readString()); + + assertEquals("unpack error Missing blob " + b.name(), r.readString()); + assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); + assertSame(PacketLineIn.END, r.readString()); + } + + @Test + public void testUsingUnknownBlobFails() throws Exception { + // Try to use the 'n' blob that is not on the server. + // + TestRepository s = new TestRepository(src); + RevBlob n = s.blob("n"); + RevCommit N = s.commit().parent(B).add("q", n).create(); + + // But don't include it in the pack. + // + final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); + packHeader(pack, 2); + copy(pack, src.open(N)); + copy(pack,src.open(s.parseBody(N).getTree())); + digest(pack); + + final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); + final PacketLineOut inPckLine = new PacketLineOut(inBuf); + inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' + + "refs/heads/s" + '\0' + + BasePackPushConnection.CAPABILITY_REPORT_STATUS); + inPckLine.end(); + pack.writeTo(inBuf, PM); + + final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); + final ReceivePack rp = new ReceivePack(dst); + rp.setCheckReceivedObjects(true); + rp.setCheckReferencedObjectsAreReachable(true); + rp.setAdvertiseRefsHook(new HidePrivateHook()); + try { + receive(rp, inBuf, outBuf); + fail("Expected UnpackException"); + } catch (UnpackException failed) { + Throwable err = failed.getCause(); + assertTrue(err instanceof MissingObjectException); + MissingObjectException moe = (MissingObjectException) err; + assertEquals(n, moe.getObjectId()); + } + + final PacketLineIn r = asPacketLineIn(outBuf); + String master = r.readString(); + int nul = master.indexOf('\0'); + assertTrue("has capability list", nul > 0); + assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); + assertSame(PacketLineIn.END, r.readString()); + + assertEquals("unpack error Missing blob " + n.name(), r.readString()); + assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); + assertSame(PacketLineIn.END, r.readString()); + } + + @Test + public void testUsingUnknownTreeFails() throws Exception { + TestRepository s = new TestRepository(src); + RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create(); + RevTree t = s.parseBody(N).getTree(); + + // Don't include the tree in the pack. + // + final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); + packHeader(pack, 1); + copy(pack, src.open(N)); + digest(pack); + + final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); + final PacketLineOut inPckLine = new PacketLineOut(inBuf); + inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' + + "refs/heads/s" + '\0' + + BasePackPushConnection.CAPABILITY_REPORT_STATUS); + inPckLine.end(); + pack.writeTo(inBuf, PM); + + final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); + final ReceivePack rp = new ReceivePack(dst); + rp.setCheckReceivedObjects(true); + rp.setCheckReferencedObjectsAreReachable(true); + rp.setAdvertiseRefsHook(new HidePrivateHook()); + try { + receive(rp, inBuf, outBuf); + fail("Expected UnpackException"); + } catch (UnpackException failed) { + Throwable err = failed.getCause(); + assertTrue(err instanceof MissingObjectException); + MissingObjectException moe = (MissingObjectException) err; + assertEquals(t, moe.getObjectId()); + } + + final PacketLineIn r = asPacketLineIn(outBuf); + String master = r.readString(); + int nul = master.indexOf('\0'); + assertTrue("has capability list", nul > 0); + assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); + assertSame(PacketLineIn.END, r.readString()); + + assertEquals("unpack error Missing tree " + t.name(), r.readString()); + assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); + assertSame(PacketLineIn.END, r.readString()); + } + + private void packHeader(TemporaryBuffer.Heap tinyPack, int cnt) + throws IOException { + final byte[] hdr = new byte[8]; + NB.encodeInt32(hdr, 0, 2); + NB.encodeInt32(hdr, 4, cnt); + + tinyPack.write(Constants.PACK_SIGNATURE); + tinyPack.write(hdr, 0, 8); + } + + private void copy(TemporaryBuffer.Heap tinyPack, ObjectLoader ldr) + throws IOException { + final byte[] buf = new byte[64]; + final byte[] content = ldr.getCachedBytes(); + int dataLength = content.length; + int nextLength = dataLength >>> 4; + int size = 0; + buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) + | (ldr.getType() << 4) | (dataLength & 0x0F)); + dataLength = nextLength; + while (dataLength > 0) { + nextLength >>>= 7; + buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F)); + dataLength = nextLength; + } + tinyPack.write(buf, 0, size); + deflate(tinyPack, content); + } + + private void deflate(TemporaryBuffer.Heap tinyPack, final byte[] content) + throws IOException { + final Deflater deflater = new Deflater(); + final byte[] buf = new byte[128]; + deflater.setInput(content, 0, content.length); + deflater.finish(); + do { + final int n = deflater.deflate(buf, 0, buf.length); + if (n > 0) + tinyPack.write(buf, 0, n); + } while (!deflater.finished()); + } + + private void digest(TemporaryBuffer.Heap buf) throws IOException { + MessageDigest md = Constants.newMessageDigest(); + md.update(buf.toByteArray()); + buf.write(md.digest()); + } + + private ObjectInserter inserter; + + @After + public void release() { + if (inserter != null) + inserter.release(); + } + + private void openPack(TemporaryBuffer.Heap buf) throws IOException { + if (inserter == null) + inserter = src.newObjectInserter(); + + final byte[] raw = buf.toByteArray(); + PackParser p = inserter.newPackParser(new ByteArrayInputStream(raw)); + p.setAllowThin(true); + p.parse(PM); + } + + private static PacketLineIn asPacketLineIn(TemporaryBuffer.Heap buf) + throws IOException { + return new PacketLineIn(new ByteArrayInputStream(buf.toByteArray())); + } + + private static final class HidePrivateHook extends AbstractAdvertiseRefsHook { + public Map getAdvertisedRefs(Repository r, RevWalk revWalk) { + Map refs = new HashMap(r.getAllRefs()); + assertNotNull(refs.remove(R_PRIVATE)); + return refs; + } + } + + private static URIish uriOf(Repository r) throws URISyntaxException { + return new URIish(r.getDirectory().getAbsolutePath()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java deleted file mode 100644 index c58252a230..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java +++ /dev/null @@ -1,574 +0,0 @@ -/* - * 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.transport; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.URISyntaxException; -import java.security.MessageDigest; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.Deflater; - -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.UnpackException; -import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; -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.ObjectInserter; -import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevBlob; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevTree; -import org.eclipse.jgit.storage.file.ObjectDirectory; -import org.eclipse.jgit.storage.pack.BinaryDelta; -import org.eclipse.jgit.util.NB; -import org.eclipse.jgit.util.TemporaryBuffer; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class ReceivePackRefFilterTest extends LocalDiskRepositoryTestCase { - private static final NullProgressMonitor PM = NullProgressMonitor.INSTANCE; - - private static final String R_MASTER = Constants.R_HEADS + Constants.MASTER; - - private static final String R_PRIVATE = Constants.R_HEADS + "private"; - - private Repository src; - - private Repository dst; - - private RevCommit A, B, P; - - private RevBlob a, b; - - @Override - @Before - public void setUp() throws Exception { - super.setUp(); - - src = createBareRepository(); - dst = createBareRepository(); - - // Fill dst with a some common history. - // - TestRepository d = new TestRepository(dst); - a = d.blob("a"); - A = d.commit(d.tree(d.file("a", a))); - B = d.commit().parent(A).create(); - d.update(R_MASTER, B); - - // Clone from dst into src - // - Transport t = Transport.open(src, uriOf(dst)); - try { - t.fetch(PM, Collections.singleton(new RefSpec("+refs/*:refs/*"))); - assertEquals(B, src.resolve(R_MASTER)); - } finally { - t.close(); - } - - // Now put private stuff into dst. - // - b = d.blob("b"); - P = d.commit(d.tree(d.file("b", b)), A); - d.update(R_PRIVATE, P); - } - - @Override - @After - public void tearDown() throws Exception { - if (src != null) - src.close(); - if (dst != null) - dst.close(); - super.tearDown(); - } - - @Test - public void testFilterHidesPrivate() throws Exception { - Map refs; - TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) { - @Override - ReceivePack createReceivePack(final Repository db) { - db.close(); - dst.incrementOpen(); - - final ReceivePack rp = super.createReceivePack(dst); - rp.setRefFilter(new HidePrivateFilter()); - return rp; - } - }; - try { - PushConnection c = t.openPush(); - try { - refs = c.getRefsMap(); - } finally { - c.close(); - } - } finally { - t.close(); - } - - assertNotNull(refs); - assertNull("no private", refs.get(R_PRIVATE)); - assertNull("no HEAD", refs.get(Constants.HEAD)); - assertEquals(1, refs.size()); - - Ref master = refs.get(R_MASTER); - assertNotNull("has master", master); - assertEquals(B, master.getObjectId()); - } - - @Test - public void testSuccess() throws Exception { - // Manually force a delta of an object so we reuse it later. - // - TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); - - packHeader(pack, 2); - pack.write((Constants.OBJ_BLOB) << 4 | 1); - deflate(pack, new byte[] { 'a' }); - - pack.write((Constants.OBJ_REF_DELTA) << 4 | 4); - a.copyRawTo(pack); - deflate(pack, new byte[] { 0x1, 0x1, 0x1, 'b' }); - - digest(pack); - openPack(pack); - - // Verify the only storage of b is our packed delta above. - // - ObjectDirectory od = (ObjectDirectory) src.getObjectDatabase(); - assertTrue("has b", src.hasObject(b)); - assertFalse("b not loose", od.fileFor(b).exists()); - - // Now use b but in a different commit than what is hidden. - // - TestRepository s = new TestRepository(src); - RevCommit N = s.commit().parent(B).add("q", b).create(); - s.update(R_MASTER, N); - - // Push this new content to the remote, doing strict validation. - // - TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) { - @Override - ReceivePack createReceivePack(final Repository db) { - db.close(); - dst.incrementOpen(); - - final ReceivePack rp = super.createReceivePack(dst); - rp.setCheckReceivedObjects(true); - rp.setCheckReferencedObjectsAreReachable(true); - rp.setRefFilter(new HidePrivateFilter()); - return rp; - } - }; - RemoteRefUpdate u = new RemoteRefUpdate( // - src, // - R_MASTER, // src name - R_MASTER, // dst name - false, // do not force update - null, // local tracking branch - null // expected id - ); - PushResult r; - try { - t.setPushThin(true); - r = t.push(PM, Collections.singleton(u)); - } finally { - t.close(); - } - - assertNotNull("have result", r); - assertNull("private not advertised", r.getAdvertisedRef(R_PRIVATE)); - assertSame("master updated", RemoteRefUpdate.Status.OK, u.getStatus()); - assertEquals(N, dst.resolve(R_MASTER)); - } - - @Test - public void testCreateBranchAtHiddenCommitFails() throws Exception { - final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64); - packHeader(pack, 0); - digest(pack); - - final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256); - final PacketLineOut inPckLine = new PacketLineOut(inBuf); - inPckLine.writeString(ObjectId.zeroId().name() + ' ' + P.name() + ' ' - + "refs/heads/s" + '\0' - + BasePackPushConnection.CAPABILITY_REPORT_STATUS); - inPckLine.end(); - pack.writeTo(inBuf, PM); - - final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); - final ReceivePack rp = new ReceivePack(dst); - rp.setCheckReceivedObjects(true); - rp.setCheckReferencedObjectsAreReachable(true); - rp.setRefFilter(new HidePrivateFilter()); - try { - receive(rp, inBuf, outBuf); - fail("Expected UnpackException"); - } catch (UnpackException failed) { - Throwable err = failed.getCause(); - assertTrue(err instanceof MissingObjectException); - MissingObjectException moe = (MissingObjectException) err; - assertEquals(P, moe.getObjectId()); - } - - final PacketLineIn r = asPacketLineIn(outBuf); - String master = r.readString(); - int nul = master.indexOf('\0'); - assertTrue("has capability list", nul > 0); - assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); - assertSame(PacketLineIn.END, r.readString()); - - assertEquals("unpack error Missing commit " + P.name(), r.readString()); - assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); - assertSame(PacketLineIn.END, r.readString()); - } - - private void receive(final ReceivePack rp, - final TemporaryBuffer.Heap inBuf, final TemporaryBuffer.Heap outBuf) - throws IOException { - rp.receive(new ByteArrayInputStream(inBuf.toByteArray()), outBuf, null); - } - - @Test - public void testUsingHiddenDeltaBaseFails() throws Exception { - byte[] delta = { 0x1, 0x1, 0x1, 'c' }; - TestRepository s = new TestRepository(src); - RevCommit N = s.commit().parent(B).add("q", - s.blob(BinaryDelta.apply(dst.open(b).getCachedBytes(), delta))) - .create(); - - final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); - packHeader(pack, 3); - copy(pack, src.open(N)); - copy(pack, src.open(s.parseBody(N).getTree())); - pack.write((Constants.OBJ_REF_DELTA) << 4 | 4); - b.copyRawTo(pack); - deflate(pack, delta); - digest(pack); - - final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); - final PacketLineOut inPckLine = new PacketLineOut(inBuf); - inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' - + "refs/heads/s" + '\0' - + BasePackPushConnection.CAPABILITY_REPORT_STATUS); - inPckLine.end(); - pack.writeTo(inBuf, PM); - - final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); - final ReceivePack rp = new ReceivePack(dst); - rp.setCheckReceivedObjects(true); - rp.setCheckReferencedObjectsAreReachable(true); - rp.setRefFilter(new HidePrivateFilter()); - try { - receive(rp, inBuf, outBuf); - fail("Expected UnpackException"); - } catch (UnpackException failed) { - Throwable err = failed.getCause(); - assertTrue(err instanceof MissingObjectException); - MissingObjectException moe = (MissingObjectException) err; - assertEquals(b, moe.getObjectId()); - } - - final PacketLineIn r = asPacketLineIn(outBuf); - String master = r.readString(); - int nul = master.indexOf('\0'); - assertTrue("has capability list", nul > 0); - assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); - assertSame(PacketLineIn.END, r.readString()); - - assertEquals("unpack error Missing blob " + b.name(), r.readString()); - assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); - assertSame(PacketLineIn.END, r.readString()); - } - - @Test - public void testUsingHiddenCommonBlobFails() throws Exception { - // Try to use the 'b' blob that is hidden. - // - TestRepository s = new TestRepository(src); - RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create(); - - // But don't include it in the pack. - // - final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); - packHeader(pack, 2); - copy(pack, src.open(N)); - copy(pack,src.open(s.parseBody(N).getTree())); - digest(pack); - - final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); - final PacketLineOut inPckLine = new PacketLineOut(inBuf); - inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' - + "refs/heads/s" + '\0' - + BasePackPushConnection.CAPABILITY_REPORT_STATUS); - inPckLine.end(); - pack.writeTo(inBuf, PM); - - final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); - final ReceivePack rp = new ReceivePack(dst); - rp.setCheckReceivedObjects(true); - rp.setCheckReferencedObjectsAreReachable(true); - rp.setRefFilter(new HidePrivateFilter()); - try { - receive(rp, inBuf, outBuf); - fail("Expected UnpackException"); - } catch (UnpackException failed) { - Throwable err = failed.getCause(); - assertTrue(err instanceof MissingObjectException); - MissingObjectException moe = (MissingObjectException) err; - assertEquals(b, moe.getObjectId()); - } - - final PacketLineIn r = asPacketLineIn(outBuf); - String master = r.readString(); - int nul = master.indexOf('\0'); - assertTrue("has capability list", nul > 0); - assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); - assertSame(PacketLineIn.END, r.readString()); - - assertEquals("unpack error Missing blob " + b.name(), r.readString()); - assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); - assertSame(PacketLineIn.END, r.readString()); - } - - @Test - public void testUsingUnknownBlobFails() throws Exception { - // Try to use the 'n' blob that is not on the server. - // - TestRepository s = new TestRepository(src); - RevBlob n = s.blob("n"); - RevCommit N = s.commit().parent(B).add("q", n).create(); - - // But don't include it in the pack. - // - final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); - packHeader(pack, 2); - copy(pack, src.open(N)); - copy(pack,src.open(s.parseBody(N).getTree())); - digest(pack); - - final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); - final PacketLineOut inPckLine = new PacketLineOut(inBuf); - inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' - + "refs/heads/s" + '\0' - + BasePackPushConnection.CAPABILITY_REPORT_STATUS); - inPckLine.end(); - pack.writeTo(inBuf, PM); - - final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); - final ReceivePack rp = new ReceivePack(dst); - rp.setCheckReceivedObjects(true); - rp.setCheckReferencedObjectsAreReachable(true); - rp.setRefFilter(new HidePrivateFilter()); - try { - receive(rp, inBuf, outBuf); - fail("Expected UnpackException"); - } catch (UnpackException failed) { - Throwable err = failed.getCause(); - assertTrue(err instanceof MissingObjectException); - MissingObjectException moe = (MissingObjectException) err; - assertEquals(n, moe.getObjectId()); - } - - final PacketLineIn r = asPacketLineIn(outBuf); - String master = r.readString(); - int nul = master.indexOf('\0'); - assertTrue("has capability list", nul > 0); - assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); - assertSame(PacketLineIn.END, r.readString()); - - assertEquals("unpack error Missing blob " + n.name(), r.readString()); - assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); - assertSame(PacketLineIn.END, r.readString()); - } - - @Test - public void testUsingUnknownTreeFails() throws Exception { - TestRepository s = new TestRepository(src); - RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create(); - RevTree t = s.parseBody(N).getTree(); - - // Don't include the tree in the pack. - // - final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); - packHeader(pack, 1); - copy(pack, src.open(N)); - digest(pack); - - final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); - final PacketLineOut inPckLine = new PacketLineOut(inBuf); - inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' - + "refs/heads/s" + '\0' - + BasePackPushConnection.CAPABILITY_REPORT_STATUS); - inPckLine.end(); - pack.writeTo(inBuf, PM); - - final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024); - final ReceivePack rp = new ReceivePack(dst); - rp.setCheckReceivedObjects(true); - rp.setCheckReferencedObjectsAreReachable(true); - rp.setRefFilter(new HidePrivateFilter()); - try { - receive(rp, inBuf, outBuf); - fail("Expected UnpackException"); - } catch (UnpackException failed) { - Throwable err = failed.getCause(); - assertTrue(err instanceof MissingObjectException); - MissingObjectException moe = (MissingObjectException) err; - assertEquals(t, moe.getObjectId()); - } - - final PacketLineIn r = asPacketLineIn(outBuf); - String master = r.readString(); - int nul = master.indexOf('\0'); - assertTrue("has capability list", nul > 0); - assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul)); - assertSame(PacketLineIn.END, r.readString()); - - assertEquals("unpack error Missing tree " + t.name(), r.readString()); - assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString()); - assertSame(PacketLineIn.END, r.readString()); - } - - private void packHeader(TemporaryBuffer.Heap tinyPack, int cnt) - throws IOException { - final byte[] hdr = new byte[8]; - NB.encodeInt32(hdr, 0, 2); - NB.encodeInt32(hdr, 4, cnt); - - tinyPack.write(Constants.PACK_SIGNATURE); - tinyPack.write(hdr, 0, 8); - } - - private void copy(TemporaryBuffer.Heap tinyPack, ObjectLoader ldr) - throws IOException { - final byte[] buf = new byte[64]; - final byte[] content = ldr.getCachedBytes(); - int dataLength = content.length; - int nextLength = dataLength >>> 4; - int size = 0; - buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) - | (ldr.getType() << 4) | (dataLength & 0x0F)); - dataLength = nextLength; - while (dataLength > 0) { - nextLength >>>= 7; - buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F)); - dataLength = nextLength; - } - tinyPack.write(buf, 0, size); - deflate(tinyPack, content); - } - - private void deflate(TemporaryBuffer.Heap tinyPack, final byte[] content) - throws IOException { - final Deflater deflater = new Deflater(); - final byte[] buf = new byte[128]; - deflater.setInput(content, 0, content.length); - deflater.finish(); - do { - final int n = deflater.deflate(buf, 0, buf.length); - if (n > 0) - tinyPack.write(buf, 0, n); - } while (!deflater.finished()); - } - - private void digest(TemporaryBuffer.Heap buf) throws IOException { - MessageDigest md = Constants.newMessageDigest(); - md.update(buf.toByteArray()); - buf.write(md.digest()); - } - - private ObjectInserter inserter; - - @After - public void release() { - if (inserter != null) - inserter.release(); - } - - private void openPack(TemporaryBuffer.Heap buf) throws IOException { - if (inserter == null) - inserter = src.newObjectInserter(); - - final byte[] raw = buf.toByteArray(); - PackParser p = inserter.newPackParser(new ByteArrayInputStream(raw)); - p.setAllowThin(true); - p.parse(PM); - } - - private static PacketLineIn asPacketLineIn(TemporaryBuffer.Heap buf) - throws IOException { - return new PacketLineIn(new ByteArrayInputStream(buf.toByteArray())); - } - - private static final class HidePrivateFilter implements RefFilter { - public Map filter(Map refs) { - Map r = new HashMap(refs); - assertNotNull(r.remove(R_PRIVATE)); - return r; - } - } - - private static URIish uriOf(Repository r) throws URISyntaxException { - return new URIish(r.getDirectory().getAbsolutePath()); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java new file mode 100644 index 0000000000..d17e074d6c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2012, Google Inc. + * and other copyright owners as documented in the project's IP log. + * * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevWalk; + +/** + * Implementation of {@link AdvertiseRefsHook} that advertises the same refs for + * upload-pack and receive-pack. + */ +public abstract class AbstractAdvertiseRefsHook implements AdvertiseRefsHook { + public void advertiseRefs(UploadPack uploadPack) + throws ServiceMayNotContinueException { + uploadPack.setAdvertisedRefs(getAdvertisedRefs( + uploadPack.getRepository(), uploadPack.getRevWalk())); + } + + public void advertiseRefs(ReceivePack receivePack) + throws ServiceMayNotContinueException { + Map refs = getAdvertisedRefs(receivePack.getRepository(), + receivePack.getRevWalk()); + Set haves = getAdvertisedHaves(receivePack.getRepository(), + receivePack.getRevWalk()); + receivePack.setAdvertisedRefs(refs, haves); + } + + /** + * Get the refs to advertise. + * + * @param repository + * repository instance. + * @param revWalk + * open rev walk on the repository. + * @return set of refs to advertise. + * @throws ServiceMayNotContinueException + * abort; the message will be sent to the user. + */ + protected abstract Map getAdvertisedRefs( + Repository repository, RevWalk revWalk) + throws ServiceMayNotContinueException; + + /** + * Get the additional haves to advertise. + * + * @param repository + * repository instance. + * @param revWalk + * open rev walk on the repository. + * @return set of additional haves; see + * {@link ReceivePack#getAdvertisedObjects()}. + * @throws ServiceMayNotContinueException + * abort; the message will be sent to the user. + */ + protected Set getAdvertisedHaves( + Repository repository, RevWalk revWalk) + throws ServiceMayNotContinueException { + return null; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java new file mode 100644 index 0000000000..f61c18643f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012, Google Inc. + * and other copyright owners as documented in the project's IP log. + * * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +/** Hook to allow callers to take over advertising refs to the client. */ +public interface AdvertiseRefsHook { + /** + * A simple hook that advertises the default refs. + *

+ * The method implementations do nothing to preserve the default behavior; see + * {@link UploadPack#setAdvertisedRefs(java.util.Map)} and + * {@link ReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)}. + */ + public static final AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() { + public void advertiseRefs(UploadPack uploadPack) { + // Do nothing. + } + + public void advertiseRefs(ReceivePack receivePack) { + // Do nothing. + } + }; + + /** + * Advertise refs for upload-pack. + * + * @param uploadPack instance on which to call + * {@link UploadPack#setAdvertisedRefs(java.util.Map)} + * if necessary. + * @throws ServiceMayNotContinueException + * abort; the message will be sent to the user. + */ + public void advertiseRefs(UploadPack uploadPack) + throws ServiceMayNotContinueException; + + /** + * Advertise refs for receive-pack. + * + * @param receivePack instance on which to call + * {@link ReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)} + * if necessary. + * @throws ServiceMayNotContinueException + * abort; the message will be sent to the user. + */ + public void advertiseRefs(ReceivePack receivePack) + throws ServiceMayNotContinueException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java index e37d80bdeb..91de77da98 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java @@ -50,7 +50,7 @@ import org.eclipse.jgit.lib.ObjectId; /** * Hook invoked by {@link UploadPack} before during critical phases. *

- * If any hook function throws {@link UploadPackMayNotContinueException} then + * If any hook function throws {@link ServiceMayNotContinueException} then * processing stops immediately and the exception is thrown up the call stack. * Most phases of UploadPack will try to report the exception's message text to * the end-user over the client's protocol connection. @@ -59,41 +59,45 @@ public interface PreUploadHook { /** A simple no-op hook. */ public static final PreUploadHook NULL = new PreUploadHook() { public void onPreAdvertiseRefs(UploadPack up) - throws UploadPackMayNotContinueException { + throws ServiceMayNotContinueException { // Do nothing. } public void onBeginNegotiateRound(UploadPack up, Collection wants, int cntOffered) - throws UploadPackMayNotContinueException { + throws ServiceMayNotContinueException { // Do nothing. } public void onEndNegotiateRound(UploadPack up, Collection wants, int cntCommon, int cntNotFound, boolean ready) - throws UploadPackMayNotContinueException { + throws ServiceMayNotContinueException { // Do nothing. } public void onSendPack(UploadPack up, Collection wants, Collection haves) - throws UploadPackMayNotContinueException { + throws ServiceMayNotContinueException { // Do nothing. } }; /** * Invoked just before {@link UploadPack#sendAdvertisedRefs(RefAdvertiser)}. + *

+ * New code should prefer implementing + * {@link AdvertiseRefsHook#advertiseRefs(UploadPack)}, which is more powerful + * and may replace this method in the future. * * @param up * the upload pack instance handling the connection. - * @throws UploadPackMayNotContinueException + * @throws ServiceMayNotContinueException * abort; the message will be sent to the user. */ public void onPreAdvertiseRefs(UploadPack up) - throws UploadPackMayNotContinueException; + throws ServiceMayNotContinueException; /** * Invoked before negotiation round is started. @@ -104,12 +108,12 @@ public interface PreUploadHook { * the list of wanted objects. * @param cntOffered * number of objects the client has offered. - * @throws UploadPackMayNotContinueException + * @throws ServiceMayNotContinueException * abort; the message will be sent to the user. */ public void onBeginNegotiateRound(UploadPack up, Collection wants, int cntOffered) - throws UploadPackMayNotContinueException; + throws ServiceMayNotContinueException; /** * Invoked after a negotiation round is completed. @@ -128,13 +132,13 @@ public interface PreUploadHook { * @param ready * true if a pack is ready to be sent (the commit graph was * successfully cut). - * @throws UploadPackMayNotContinueException + * @throws ServiceMayNotContinueException * abort; the message will be sent to the user. */ public void onEndNegotiateRound(UploadPack up, Collection wants, int cntCommon, int cntNotFound, boolean ready) - throws UploadPackMayNotContinueException; + throws ServiceMayNotContinueException; /** * Invoked just before a pack will be sent to the client. @@ -149,10 +153,10 @@ public interface PreUploadHook { * the list of common objects. Empty on an initial clone request. * These may be RevObject or RevCommit if the processed parsed * them. Implementors should not rely on the values being parsed. - * @throws UploadPackMayNotContinueException + * @throws ServiceMayNotContinueException * abort; the message will be sent to the user. */ public void onSendPack(UploadPack up, Collection wants, Collection haves) - throws UploadPackMayNotContinueException; + throws ServiceMayNotContinueException; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java index 93f8eef0cb..25323eb5f2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java @@ -80,14 +80,14 @@ public class PreUploadHookChain implements PreUploadHook { } public void onPreAdvertiseRefs(UploadPack up) - throws UploadPackMayNotContinueException { + throws ServiceMayNotContinueException { for (int i = 0; i < count; i++) hooks[i].onPreAdvertiseRefs(up); } public void onBeginNegotiateRound(UploadPack up, Collection wants, int cntOffered) - throws UploadPackMayNotContinueException { + throws ServiceMayNotContinueException { for (int i = 0; i < count; i++) hooks[i].onBeginNegotiateRound(up, wants, cntOffered); } @@ -95,7 +95,7 @@ public class PreUploadHookChain implements PreUploadHook { public void onEndNegotiateRound(UploadPack up, Collection wants, int cntCommon, int cntNotFound, boolean ready) - throws UploadPackMayNotContinueException { + throws ServiceMayNotContinueException { for (int i = 0; i < count; i++) hooks[i].onEndNegotiateRound(up, wants, cntCommon, cntNotFound, ready); } @@ -103,7 +103,7 @@ public class PreUploadHookChain implements PreUploadHook { public void onSendPack(UploadPack up, Collection wants, Collection haves) - throws UploadPackMayNotContinueException { + throws ServiceMayNotContinueException { for (int i = 0; i < count; i++) hooks[i].onSendPack(up, wants, haves); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java index 6602de49ed..0d79110289 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -134,6 +134,9 @@ public class ReceivePack { /** Identity to record action as within the reflog. */ private PersonIdent refLogIdent; + /** Hook used while advertising the refs to the client. */ + private AdvertiseRefsHook advertiseRefsHook; + /** Filter used while advertising the refs to the client. */ private RefFilter refFilter; @@ -213,6 +216,7 @@ public class ReceivePack { allowDeletes = cfg.allowDeletes; allowNonFastForwards = cfg.allowNonFastForwards; allowOfsDelta = cfg.allowOfsDelta; + advertiseRefsHook = AdvertiseRefsHook.DEFAULT; refFilter = RefFilter.DEFAULT; preReceive = PreReceiveHook.NULL; postReceive = PostReceiveHook.NULL; @@ -304,27 +308,59 @@ public class ReceivePack { return walk; } - /** @return all refs which were advertised to the client. */ + /** + * Get refs which were advertised to the client. + * + * @return all refs which were advertised to the client, or null if + * {@link #setAdvertisedRefs(Map, Set)} has not been called yet. + */ public final Map getAdvertisedRefs() { - if (refs == null) { - refs = refFilter.filter(db.getAllRefs()); - - Ref head = refs.get(Constants.HEAD); - if (head != null && head.isSymbolic()) - refs.remove(Constants.HEAD); + return refs; + } - for (Ref ref : refs.values()) { - if (ref.getObjectId() != null) - advertisedHaves.add(ref.getObjectId()); - } - advertisedHaves.addAll(db.getAdditionalHaves()); + /** + * Set the refs advertised by this ReceivePack. + *

+ * Intended to be called from a {@link PreReceiveHook}. + * + * @param allRefs + * explicit set of references to claim as advertised by this + * ReceivePack instance. This overrides any references that + * may exist in the source repository. The map is passed + * to the configured {@link #getRefFilter()}. If null, assumes + * all refs were advertised. + * @param additionalHaves + * explicit set of additional haves to claim as advertised. If + * null, assumes the default set of additional haves from the + * repository. + */ + public void setAdvertisedRefs(Map allRefs, + Set additionalHaves) { + refs = allRefs != null ? allRefs : db.getAllRefs(); + refs = refFilter.filter(refs); + + Ref head = refs.get(Constants.HEAD); + if (head != null && head.isSymbolic()) + refs.remove(Constants.HEAD); + + for (Ref ref : refs.values()) { + if (ref.getObjectId() != null) + advertisedHaves.add(ref.getObjectId()); } - return refs; + if (additionalHaves != null) + advertisedHaves.addAll(additionalHaves); + else + advertisedHaves.addAll(db.getAdditionalHaves()); } - /** @return the set of objects advertised as present in this repository. */ + /** + * Get objects advertised to the client. + * + * @return the set of objects advertised to the as present in this repository, + * or null if {@link #setAdvertisedRefs(Map, Set)} has not been called + * yet. + */ public final Set getAdvertisedObjects() { - getAdvertisedRefs(); return advertisedHaves; } @@ -342,13 +378,13 @@ public class ReceivePack { *

* If enabled, this instance will verify that references to objects not * contained within the received pack are already reachable through at least - * one other reference selected by the {@link #getRefFilter()} and displayed - * as part of {@link #getAdvertisedRefs()}. + * one other reference displayed as part of {@link #getAdvertisedRefs()}. *

* This feature is useful when the application doesn't trust the client to * not provide a forged SHA-1 reference to an object, in an attempt to * access parts of the DAG that they aren't allowed to see and which have - * been hidden from them via the configured {@link RefFilter}. + * been hidden from them via the configured {@link AdvertiseRefsHook} or + * {@link RefFilter}. *

* Enabling this feature may imply at least some, if not all, of the same * functionality performed by {@link #setCheckReceivedObjects(boolean)}. @@ -464,18 +500,42 @@ public class ReceivePack { refLogIdent = pi; } + /** @return the hook used while advertising the refs to the client */ + public AdvertiseRefsHook getAdvertiseRefsHook() { + return advertiseRefsHook; + } + /** @return the filter used while advertising the refs to the client */ public RefFilter getRefFilter() { return refFilter; } + /** + * Set the hook used while advertising the refs to the client. + *

+ * If the {@link AdvertiseRefsHook} chooses to call + * {@link #setAdvertisedRefs(Map,Set)}, only refs set by this hook + * and selected by the {@link RefFilter} will be shown to the client. + * Clients may still attempt to create or update a reference not advertised by + * the configured {@link AdvertiseRefsHook}. These attempts should be rejected + * by a matching {@link PreReceiveHook}. + * + * @param advertiseRefsHook + * the hook; may be null to show all refs. + */ + public void setAdvertiseRefsHook(final AdvertiseRefsHook advertiseRefsHook) { + if (advertiseRefsHook != null) + this.advertiseRefsHook = advertiseRefsHook; + else + this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT; + } + /** * Set the filter used while advertising the refs to the client. *

* Only refs allowed by this filter will be shown to the client. - * Clients may still attempt to create or update a reference hidden - * by the configured {@link RefFilter}. These attempts should be - * rejected by a matching {@link PreReceiveHook}. + * The filter is run against the refs specified by the + * {@link AdvertiseRefsHook} (if applicable). * * @param refFilter * the filter; may be null to show all refs. @@ -706,12 +766,18 @@ public class ReceivePack { } } + private Map getAdvertisedOrDefaultRefs() { + if (refs == null) + setAdvertisedRefs(null, null); + return refs; + } + private void service() throws IOException { if (biDirectionalPipe) { sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); pckOut.flush(); } else - getAdvertisedRefs(); + getAdvertisedOrDefaultRefs(); if (advertiseError != null) return; recvCommands(); @@ -776,20 +842,33 @@ public class ReceivePack { * the advertisement formatter. * @throws IOException * the formatter failed to write an advertisement. + * @throws ServiceMayNotContinueException + * the hook denied advertisement. */ - public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException { + public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException, + ServiceMayNotContinueException { if (advertiseError != null) { adv.writeOne("ERR " + advertiseError); return; } + try { + advertiseRefsHook.advertiseRefs(this); + } catch (ServiceMayNotContinueException fail) { + if (fail.getMessage() != null) { + adv.writeOne("ERR " + fail.getMessage()); + fail.setOutput(); + } + throw fail; + } + adv.init(db); adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K); adv.advertiseCapability(CAPABILITY_DELETE_REFS); adv.advertiseCapability(CAPABILITY_REPORT_STATUS); if (allowOfsDelta) adv.advertiseCapability(CAPABILITY_OFS_DELTA); - adv.send(getAdvertisedRefs()); + adv.send(getAdvertisedOrDefaultRefs()); for (ObjectId obj : advertisedHaves) adv.advertiseHave(obj); if (adv.isEmpty()) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java new file mode 100644 index 0000000000..bf85068f27 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2011-2012, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import java.io.IOException; + +/** Indicates a transport service may not continue execution. */ +public class ServiceMayNotContinueException extends IOException { + private static final long serialVersionUID = 1L; + + private boolean output; + + /** Initialize with no message. */ + public ServiceMayNotContinueException() { + // Do not set a message. + } + + /** + * @param msg + * a message explaining why it cannot continue. This message may + * be shown to an end-user. + */ + public ServiceMayNotContinueException(String msg) { + super(msg); + } + + /** @return true if the message was already output to the client. */ + public boolean isOutput() { + return output; + } + + /** Mark this message has being sent to the client. */ + public void setOutput() { + output = true; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index 2fa6e5cff0..debbab51fc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -157,8 +157,11 @@ public class UploadPack { /** The refs we advertised as existing at the start of the connection. */ private Map refs; + /** Hook used while advertising the refs to the client. */ + private AdvertiseRefsHook advertiseRefsHook = AdvertiseRefsHook.DEFAULT; + /** Filter used while advertising the refs to the client. */ - private RefFilter refFilter; + private RefFilter refFilter = RefFilter.DEFAULT; /** Hook handling the various upload phases. */ private PreUploadHook preUploadHook = PreUploadHook.NULL; @@ -241,7 +244,6 @@ public class UploadPack { SAVE.add(PEER_HAS); SAVE.add(COMMON); SAVE.add(SATISFIED); - refFilter = RefFilter.DEFAULT; } /** @return the repository this upload is reading from. */ @@ -254,22 +256,34 @@ public class UploadPack { return walk; } - /** @return all refs which were advertised to the client. */ + /** + * Get refs which were advertised to the client. + * + * @return all refs which were advertised to the client, or null if + * {@link #setAdvertisedRefs(Map)} has not been called yet. + */ public final Map getAdvertisedRefs() { - if (refs == null) - setAdvertisedRefs(db.getAllRefs()); return refs; } /** + * Set the refs advertised by this UploadPack. + *

+ * Intended to be called from a {@link PreUploadHook}. + * * @param allRefs * explicit set of references to claim as advertised by this * UploadPack instance. This overrides any references that * may exist in the source repository. The map is passed - * to the configured {@link #getRefFilter()}. + * to the configured {@link #getRefFilter()}. If null, assumes + * all refs were advertised. */ public void setAdvertisedRefs(Map allRefs) { - refs = refFilter.filter(allRefs); + if (allRefs != null) + refs = allRefs; + else + refs = db.getAllRefs(); + refs = refFilter.filter(refs); } /** @return timeout (in seconds) before aborting an IO operation. */ @@ -330,18 +344,39 @@ public class UploadPack { requestPolicy = policy != null ? policy : RequestPolicy.ADVERTISED; } + /** @return the hook used while advertising the refs to the client */ + public AdvertiseRefsHook getAdvertiseRefsHook() { + return advertiseRefsHook; + } + /** @return the filter used while advertising the refs to the client */ public RefFilter getRefFilter() { return refFilter; } + /** + * Set the hook used while advertising the refs to the client. + *

+ * If the {@link AdvertiseRefsHook} chooses to call + * {@link #setAdvertisedRefs(Map)}, only refs set by this hook and + * selected by the {@link RefFilter} will be shown to the client. + * + * @param advertiseRefsHook + * the hook; may be null to show all refs. + */ + public void setAdvertiseRefsHook(final AdvertiseRefsHook advertiseRefsHook) { + if (advertiseRefsHook != null) + this.advertiseRefsHook = advertiseRefsHook; + else + this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT; + } + /** * Set the filter used while advertising the refs to the client. *

- * Only refs allowed by this filter will be sent to the client. This can - * be used by a server to restrict the list of references the client can - * obtain through clone or fetch, effectively limiting the access to only - * certain refs. + * Only refs allowed by this filter will be sent to the client. + * The filter is run against the refs specified by the + * {@link AdvertiseRefsHook} (if applicable). * * @param refFilter * the filter; may be null to show all refs. @@ -451,6 +486,12 @@ public class UploadPack { return statistics; } + private Map getAdvertisedOrDefaultRefs() { + if (refs == null) + setAdvertisedRefs(null); + return refs; + } + private void service() throws IOException { if (biDirectionalPipe) sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); @@ -458,7 +499,7 @@ public class UploadPack { advertised = Collections.emptySet(); else { advertised = new HashSet(); - for (Ref ref : getAdvertisedRefs().values()) { + for (Ref ref : getAdvertisedOrDefaultRefs().values()) { if (ref.getObjectId() != null) advertised.add(ref.getObjectId()); } @@ -488,7 +529,7 @@ public class UploadPack { reportErrorDuringNegotiate(err.getMessage()); throw err; - } catch (UploadPackMayNotContinueException err) { + } catch (ServiceMayNotContinueException err) { if (!err.isOutput() && err.getMessage() != null) { try { pckOut.writeString("ERR " + err.getMessage() + "\n"); @@ -562,14 +603,15 @@ public class UploadPack { * the advertisement formatter. * @throws IOException * the formatter failed to write an advertisement. - * @throws UploadPackMayNotContinueException + * @throws ServiceMayNotContinueException * the hook denied advertisement. */ public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException, - UploadPackMayNotContinueException { + ServiceMayNotContinueException { try { preUploadHook.onPreAdvertiseRefs(this); - } catch (UploadPackMayNotContinueException fail) { + advertiseRefsHook.advertiseRefs(this); + } catch (ServiceMayNotContinueException fail) { if (fail.getMessage() != null) { adv.writeOne("ERR " + fail.getMessage()); fail.setOutput(); @@ -590,7 +632,7 @@ public class UploadPack { if (!biDirectionalPipe) adv.advertiseCapability(OPTION_NO_DONE); adv.setDerefTags(true); - advertised = adv.send(getAdvertisedRefs()); + advertised = adv.send(getAdvertisedOrDefaultRefs()); adv.end(); } @@ -959,7 +1001,7 @@ public class UploadPack { if (sideband) { try { sendPack(true); - } catch (UploadPackMayNotContinueException noPack) { + } catch (ServiceMayNotContinueException noPack) { // This was already reported on (below). throw noPack; } catch (IOException err) { @@ -1023,7 +1065,7 @@ public class UploadPack { } else { preUploadHook.onSendPack(this, wantAll, commonBase); } - } catch (UploadPackMayNotContinueException noPack) { + } catch (ServiceMayNotContinueException noPack) { if (sideband && noPack.getMessage() != null) { noPack.setOutput(); SideBandOutputStream err = new SideBandOutputStream( diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackMayNotContinueException.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackMayNotContinueException.java index c2395f37de..5392a01554 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackMayNotContinueException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackMayNotContinueException.java @@ -43,14 +43,15 @@ package org.eclipse.jgit.transport; -import java.io.IOException; - -/** Indicates UploadPack may not continue execution. */ -public class UploadPackMayNotContinueException extends IOException { +/** + * Indicates UploadPack may not continue execution. + * + * @deprecated use {@link ServiceMayNotContinueException} instead. + */ +@Deprecated +public class UploadPackMayNotContinueException extends ServiceMayNotContinueException { private static final long serialVersionUID = 1L; - private boolean output; - /** Initialize with no message. */ public UploadPackMayNotContinueException() { // Do not set a message. @@ -64,14 +65,4 @@ public class UploadPackMayNotContinueException extends IOException { public UploadPackMayNotContinueException(String msg) { super(msg); } - - /** @return true if the message was already output to the client. */ - public boolean isOutput() { - return output; - } - - /** Mark this message has being sent to the client. */ - public void setOutput() { - output = true; - } } -- cgit v1.2.3