| /* |
| * Copyright (C) 2010, Google Inc. |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.eclipse.jgit.http.test; |
| |
| import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING; |
| import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH; |
| import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE; |
| 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.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.net.URISyntaxException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.servlet.DispatcherType; |
| import javax.servlet.Filter; |
| import javax.servlet.FilterChain; |
| import javax.servlet.FilterConfig; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletRequest; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.eclipse.jetty.servlet.FilterHolder; |
| import org.eclipse.jetty.servlet.ServletContextHandler; |
| import org.eclipse.jetty.servlet.ServletHolder; |
| import org.eclipse.jgit.errors.RemoteRepositoryException; |
| import org.eclipse.jgit.errors.RepositoryNotFoundException; |
| import org.eclipse.jgit.errors.TransportException; |
| import org.eclipse.jgit.http.server.GitServlet; |
| import org.eclipse.jgit.internal.JGitText; |
| import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; |
| import org.eclipse.jgit.junit.TestRepository; |
| import org.eclipse.jgit.junit.TestRng; |
| import org.eclipse.jgit.junit.http.AccessEvent; |
| import org.eclipse.jgit.junit.http.AppServer; |
| import org.eclipse.jgit.junit.http.HttpTestCase; |
| import org.eclipse.jgit.lib.ConfigConstants; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.NullProgressMonitor; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectIdRef; |
| import org.eclipse.jgit.lib.ObjectInserter; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.ReflogEntry; |
| import org.eclipse.jgit.lib.ReflogReader; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.lib.StoredConfig; |
| import org.eclipse.jgit.revwalk.RevBlob; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.transport.FetchConnection; |
| import org.eclipse.jgit.transport.HttpTransport; |
| import org.eclipse.jgit.transport.RemoteRefUpdate; |
| import org.eclipse.jgit.transport.Transport; |
| import org.eclipse.jgit.transport.TransportHttp; |
| import org.eclipse.jgit.transport.URIish; |
| import org.eclipse.jgit.transport.http.HttpConnectionFactory; |
| import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory; |
| import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; |
| import org.eclipse.jgit.transport.resolver.RepositoryResolver; |
| import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; |
| import org.eclipse.jgit.util.HttpSupport; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| @RunWith(Parameterized.class) |
| public class SmartClientSmartServerTest extends HttpTestCase { |
| private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding"; |
| |
| private Repository remoteRepository; |
| |
| private URIish remoteURI; |
| |
| private URIish brokenURI; |
| |
| private URIish redirectURI; |
| |
| private RevBlob A_txt; |
| |
| private RevCommit A, B; |
| |
| @Parameters |
| public static Collection<Object[]> data() { |
| // run all tests with both connection factories we have |
| return Arrays.asList(new Object[][] { |
| { new JDKHttpConnectionFactory() }, |
| { new HttpClientConnectionFactory() } }); |
| } |
| |
| public SmartClientSmartServerTest(HttpConnectionFactory cf) { |
| HttpTransport.setConnectionFactory(cf); |
| } |
| |
| @Override |
| @Before |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| final TestRepository<Repository> src = createTestRepository(); |
| final String srcName = src.getRepository().getDirectory().getName(); |
| src.getRepository() |
| .getConfig() |
| .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, |
| ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); |
| |
| GitServlet gs = new GitServlet(); |
| |
| ServletContextHandler app = addNormalContext(gs, src, srcName); |
| |
| ServletContextHandler broken = addBrokenContext(gs, src, srcName); |
| |
| ServletContextHandler redirect = addRedirectContext(gs, src, srcName); |
| |
| server.setUp(); |
| |
| remoteRepository = src.getRepository(); |
| remoteURI = toURIish(app, srcName); |
| brokenURI = toURIish(broken, srcName); |
| redirectURI = toURIish(redirect, srcName); |
| |
| A_txt = src.blob("A"); |
| A = src.commit().add("A_txt", A_txt).create(); |
| B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create(); |
| src.update(master, B); |
| |
| src.update("refs/garbage/a/very/long/ref/name/to/compress", B); |
| } |
| |
| private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) { |
| ServletContextHandler app = server.addContext("/git"); |
| gs.setRepositoryResolver(new TestRepoResolver(src, srcName)); |
| app.addServlet(new ServletHolder(gs), "/*"); |
| return app; |
| } |
| |
| @SuppressWarnings("unused") |
| private ServletContextHandler addBrokenContext(GitServlet gs, TestRepository<Repository> src, String srcName) { |
| ServletContextHandler broken = server.addContext("/bad"); |
| broken.addFilter(new FilterHolder(new Filter() { |
| |
| @Override |
| public void doFilter(ServletRequest request, |
| ServletResponse response, FilterChain chain) |
| throws IOException, ServletException { |
| final HttpServletResponse r = (HttpServletResponse) response; |
| r.setContentType("text/plain"); |
| r.setCharacterEncoding("UTF-8"); |
| PrintWriter w = r.getWriter(); |
| w.print("OK"); |
| w.close(); |
| } |
| |
| @Override |
| public void init(FilterConfig filterConfig) |
| throws ServletException { |
| // empty |
| } |
| |
| @Override |
| public void destroy() { |
| // empty |
| } |
| }), "/" + srcName + "/git-upload-pack", |
| EnumSet.of(DispatcherType.REQUEST)); |
| broken.addServlet(new ServletHolder(gs), "/*"); |
| return broken; |
| } |
| |
| @SuppressWarnings("unused") |
| private ServletContextHandler addRedirectContext(GitServlet gs, |
| TestRepository<Repository> src, String srcName) { |
| ServletContextHandler redirect = server.addContext("/redirect"); |
| redirect.addFilter(new FilterHolder(new Filter() { |
| |
| @Override |
| public void init(FilterConfig filterConfig) |
| throws ServletException { |
| // empty |
| } |
| |
| @Override |
| public void doFilter(ServletRequest request, |
| ServletResponse response, FilterChain chain) |
| throws IOException, ServletException { |
| final HttpServletResponse httpServletResponse = (HttpServletResponse) response; |
| final HttpServletRequest httpServletRequest = (HttpServletRequest) request; |
| final StringBuffer fullUrl = httpServletRequest.getRequestURL(); |
| if (httpServletRequest.getQueryString() != null) { |
| fullUrl.append("?") |
| .append(httpServletRequest.getQueryString()); |
| } |
| httpServletResponse |
| .setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); |
| httpServletResponse.setHeader(HttpSupport.HDR_LOCATION, |
| fullUrl.toString().replace("/redirect", "/git")); |
| } |
| |
| @Override |
| public void destroy() { |
| // empty |
| } |
| }), "/*", EnumSet.of(DispatcherType.REQUEST)); |
| redirect.addServlet(new ServletHolder(gs), "/*"); |
| return redirect; |
| } |
| |
| @Test |
| public void testListRemote() throws IOException { |
| Repository dst = createBareRepository(); |
| |
| assertEquals("http", remoteURI.getScheme()); |
| |
| Map<String, Ref> map; |
| try (Transport t = Transport.open(dst, remoteURI)) { |
| // I didn't make up these public interface names, I just |
| // approved them for inclusion into the code base. Sorry. |
| // --spearce |
| // |
| assertTrue("isa TransportHttp", t instanceof TransportHttp); |
| assertTrue("isa HttpTransport", t instanceof HttpTransport); |
| |
| FetchConnection c = t.openFetch(); |
| try { |
| map = c.getRefsMap(); |
| } finally { |
| c.close(); |
| } |
| } |
| |
| assertNotNull("have map of refs", map); |
| assertEquals(3, map.size()); |
| |
| assertNotNull("has " + master, map.get(master)); |
| assertEquals(B, map.get(master).getObjectId()); |
| |
| assertNotNull("has " + Constants.HEAD, map.get(Constants.HEAD)); |
| assertEquals(B, map.get(Constants.HEAD).getObjectId()); |
| |
| List<AccessEvent> requests = getRequests(); |
| assertEquals(1, requests.size()); |
| |
| AccessEvent info = requests.get(0); |
| assertEquals("GET", info.getMethod()); |
| assertEquals(join(remoteURI, "info/refs"), info.getPath()); |
| assertEquals(1, info.getParameters().size()); |
| assertEquals("git-upload-pack", info.getParameter("service")); |
| assertEquals(200, info.getStatus()); |
| assertEquals("application/x-git-upload-pack-advertisement", info |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); |
| } |
| |
| @Test |
| public void testListRemote_BadName() throws IOException, URISyntaxException { |
| Repository dst = createBareRepository(); |
| URIish uri = new URIish(this.remoteURI.toString() + ".invalid"); |
| try (Transport t = Transport.open(dst, uri)) { |
| try { |
| t.openFetch(); |
| fail("fetch connection opened"); |
| } catch (RemoteRepositoryException notFound) { |
| assertEquals(uri + ": Git repository not found", |
| notFound.getMessage()); |
| } |
| } |
| |
| List<AccessEvent> requests = getRequests(); |
| assertEquals(1, requests.size()); |
| |
| AccessEvent info = requests.get(0); |
| assertEquals("GET", info.getMethod()); |
| assertEquals(join(uri, "info/refs"), info.getPath()); |
| assertEquals(1, info.getParameters().size()); |
| assertEquals("git-upload-pack", info.getParameter("service")); |
| assertEquals(200, info.getStatus()); |
| assertEquals("application/x-git-upload-pack-advertisement", |
| info.getResponseHeader(HDR_CONTENT_TYPE)); |
| } |
| |
| @Test |
| public void testInitialClone_Small() throws Exception { |
| Repository dst = createBareRepository(); |
| assertFalse(dst.hasObject(A_txt)); |
| |
| try (Transport t = Transport.open(dst, remoteURI)) { |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| } |
| |
| assertTrue(dst.hasObject(A_txt)); |
| assertEquals(B, dst.exactRef(master).getObjectId()); |
| fsck(dst, B); |
| |
| List<AccessEvent> requests = getRequests(); |
| assertEquals(2, requests.size()); |
| |
| AccessEvent info = requests.get(0); |
| assertEquals("GET", info.getMethod()); |
| assertEquals(join(remoteURI, "info/refs"), info.getPath()); |
| assertEquals(1, info.getParameters().size()); |
| assertEquals("git-upload-pack", info.getParameter("service")); |
| assertEquals(200, info.getStatus()); |
| assertEquals("application/x-git-upload-pack-advertisement", info |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); |
| |
| AccessEvent service = requests.get(1); |
| assertEquals("POST", service.getMethod()); |
| assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); |
| assertEquals(0, service.getParameters().size()); |
| assertNotNull("has content-length", service |
| .getRequestHeader(HDR_CONTENT_LENGTH)); |
| assertNull("not chunked", service |
| .getRequestHeader(HDR_TRANSFER_ENCODING)); |
| |
| assertEquals(200, service.getStatus()); |
| assertEquals("application/x-git-upload-pack-result", service |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| } |
| |
| @Test |
| public void testInitialClone_RedirectSmall() throws Exception { |
| Repository dst = createBareRepository(); |
| assertFalse(dst.hasObject(A_txt)); |
| |
| try (Transport t = Transport.open(dst, redirectURI)) { |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| } |
| |
| assertTrue(dst.hasObject(A_txt)); |
| assertEquals(B, dst.exactRef(master).getObjectId()); |
| fsck(dst, B); |
| |
| List<AccessEvent> requests = getRequests(); |
| assertEquals(4, requests.size()); |
| |
| AccessEvent firstRedirect = requests.get(0); |
| assertEquals(301, firstRedirect.getStatus()); |
| |
| AccessEvent info = requests.get(1); |
| assertEquals("GET", info.getMethod()); |
| assertEquals(join(remoteURI, "info/refs"), info.getPath()); |
| assertEquals(1, info.getParameters().size()); |
| assertEquals("git-upload-pack", info.getParameter("service")); |
| assertEquals(200, info.getStatus()); |
| assertEquals("application/x-git-upload-pack-advertisement", |
| info.getResponseHeader(HDR_CONTENT_TYPE)); |
| assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); |
| |
| AccessEvent secondRedirect = requests.get(2); |
| assertEquals(301, secondRedirect.getStatus()); |
| |
| AccessEvent service = requests.get(3); |
| assertEquals("POST", service.getMethod()); |
| assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); |
| assertEquals(0, service.getParameters().size()); |
| assertNotNull("has content-length", |
| service.getRequestHeader(HDR_CONTENT_LENGTH)); |
| assertNull("not chunked", |
| service.getRequestHeader(HDR_TRANSFER_ENCODING)); |
| |
| assertEquals(200, service.getStatus()); |
| assertEquals("application/x-git-upload-pack-result", |
| service.getResponseHeader(HDR_CONTENT_TYPE)); |
| } |
| |
| @Test |
| public void testFetch_FewLocalCommits() throws Exception { |
| // Bootstrap by doing the clone. |
| // |
| TestRepository dst = createTestRepository(); |
| try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| } |
| assertEquals(B, dst.getRepository().exactRef(master).getObjectId()); |
| List<AccessEvent> cloneRequests = getRequests(); |
| |
| // Only create a few new commits. |
| TestRepository.BranchBuilder b = dst.branch(master); |
| for (int i = 0; i < 4; i++) |
| b.commit().tick(3600 /* 1 hour */).message("c" + i).create(); |
| |
| // Create a new commit on the remote. |
| // |
| b = new TestRepository<>(remoteRepository).branch(master); |
| RevCommit Z = b.commit().message("Z").create(); |
| |
| // Now incrementally update. |
| // |
| try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| } |
| assertEquals(Z, dst.getRepository().exactRef(master).getObjectId()); |
| |
| List<AccessEvent> requests = getRequests(); |
| requests.removeAll(cloneRequests); |
| assertEquals(2, requests.size()); |
| |
| AccessEvent info = requests.get(0); |
| assertEquals("GET", info.getMethod()); |
| assertEquals(join(remoteURI, "info/refs"), info.getPath()); |
| assertEquals(1, info.getParameters().size()); |
| assertEquals("git-upload-pack", info.getParameter("service")); |
| assertEquals(200, info.getStatus()); |
| assertEquals("application/x-git-upload-pack-advertisement", |
| info.getResponseHeader(HDR_CONTENT_TYPE)); |
| |
| // We should have needed one request to perform the fetch. |
| // |
| AccessEvent service = requests.get(1); |
| assertEquals("POST", service.getMethod()); |
| assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); |
| assertEquals(0, service.getParameters().size()); |
| assertNotNull("has content-length", |
| service.getRequestHeader(HDR_CONTENT_LENGTH)); |
| assertNull("not chunked", |
| service.getRequestHeader(HDR_TRANSFER_ENCODING)); |
| |
| assertEquals(200, service.getStatus()); |
| assertEquals("application/x-git-upload-pack-result", |
| service.getResponseHeader(HDR_CONTENT_TYPE)); |
| } |
| |
| @Test |
| public void testFetch_TooManyLocalCommits() throws Exception { |
| // Bootstrap by doing the clone. |
| // |
| TestRepository dst = createTestRepository(); |
| try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| } |
| assertEquals(B, dst.getRepository().exactRef(master).getObjectId()); |
| List<AccessEvent> cloneRequests = getRequests(); |
| |
| // Force enough into the local client that enumeration will |
| // need multiple packets, but not too many to overflow and |
| // not pick up the ACK_COMMON message. |
| // |
| TestRepository.BranchBuilder b = dst.branch(master); |
| for (int i = 0; i < 32 - 1; i++) |
| b.commit().tick(3600 /* 1 hour */).message("c" + i).create(); |
| |
| // Create a new commit on the remote. |
| // |
| b = new TestRepository<>(remoteRepository).branch(master); |
| RevCommit Z = b.commit().message("Z").create(); |
| |
| // Now incrementally update. |
| // |
| try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| } |
| assertEquals(Z, dst.getRepository().exactRef(master).getObjectId()); |
| |
| List<AccessEvent> requests = getRequests(); |
| requests.removeAll(cloneRequests); |
| assertEquals(3, requests.size()); |
| |
| AccessEvent info = requests.get(0); |
| assertEquals("GET", info.getMethod()); |
| assertEquals(join(remoteURI, "info/refs"), info.getPath()); |
| assertEquals(1, info.getParameters().size()); |
| assertEquals("git-upload-pack", info.getParameter("service")); |
| assertEquals(200, info.getStatus()); |
| assertEquals("application/x-git-upload-pack-advertisement", info |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| |
| // We should have needed two requests to perform the fetch |
| // due to the high number of local unknown commits. |
| // |
| AccessEvent service = requests.get(1); |
| assertEquals("POST", service.getMethod()); |
| assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); |
| assertEquals(0, service.getParameters().size()); |
| assertNotNull("has content-length", service |
| .getRequestHeader(HDR_CONTENT_LENGTH)); |
| assertNull("not chunked", service |
| .getRequestHeader(HDR_TRANSFER_ENCODING)); |
| |
| assertEquals(200, service.getStatus()); |
| assertEquals("application/x-git-upload-pack-result", service |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| |
| service = requests.get(2); |
| assertEquals("POST", service.getMethod()); |
| assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); |
| assertEquals(0, service.getParameters().size()); |
| assertNotNull("has content-length", service |
| .getRequestHeader(HDR_CONTENT_LENGTH)); |
| assertNull("not chunked", service |
| .getRequestHeader(HDR_TRANSFER_ENCODING)); |
| |
| assertEquals(200, service.getStatus()); |
| assertEquals("application/x-git-upload-pack-result", service |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| } |
| |
| @Test |
| public void testInitialClone_BrokenServer() throws Exception { |
| Repository dst = createBareRepository(); |
| assertFalse(dst.hasObject(A_txt)); |
| |
| try (Transport t = Transport.open(dst, brokenURI)) { |
| try { |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| fail("fetch completed despite upload-pack being broken"); |
| } catch (TransportException err) { |
| String exp = brokenURI + ": expected" |
| + " Content-Type application/x-git-upload-pack-result;" |
| + " received Content-Type text/plain;charset=utf-8"; |
| assertEquals(exp, err.getMessage()); |
| } |
| } |
| |
| List<AccessEvent> requests = getRequests(); |
| assertEquals(2, requests.size()); |
| |
| AccessEvent info = requests.get(0); |
| assertEquals("GET", info.getMethod()); |
| assertEquals(join(brokenURI, "info/refs"), info.getPath()); |
| assertEquals(1, info.getParameters().size()); |
| assertEquals("git-upload-pack", info.getParameter("service")); |
| assertEquals(200, info.getStatus()); |
| assertEquals("application/x-git-upload-pack-advertisement", info |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| |
| AccessEvent service = requests.get(1); |
| assertEquals("POST", service.getMethod()); |
| assertEquals(join(brokenURI, "git-upload-pack"), service.getPath()); |
| assertEquals(0, service.getParameters().size()); |
| assertEquals(200, service.getStatus()); |
| assertEquals("text/plain;charset=utf-8", |
| service.getResponseHeader(HDR_CONTENT_TYPE)); |
| } |
| |
| @Test |
| public void testInvalidWant() throws Exception { |
| @SuppressWarnings("resource") |
| ObjectId id = new ObjectInserter.Formatter().idFor(Constants.OBJ_BLOB, |
| "testInvalidWant".getBytes(StandardCharsets.UTF_8)); |
| |
| Repository dst = createBareRepository(); |
| try (Transport t = Transport.open(dst, remoteURI); |
| FetchConnection c = t.openFetch()) { |
| Ref want = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(), |
| id); |
| c.fetch(NullProgressMonitor.INSTANCE, Collections.singleton(want), |
| Collections.<ObjectId> emptySet()); |
| fail("Server accepted want " + id.name()); |
| } catch (TransportException err) { |
| assertEquals("want " + id.name() + " not valid", err.getMessage()); |
| } |
| } |
| |
| @Test |
| public void testFetch_RefsUnreadableOnUpload() throws Exception { |
| AppServer noRefServer = new AppServer(); |
| try { |
| final String repoName = "refs-unreadable"; |
| RefsUnreadableInMemoryRepository badRefsRepo = new RefsUnreadableInMemoryRepository( |
| new DfsRepositoryDescription(repoName)); |
| final TestRepository<Repository> repo = new TestRepository<>( |
| badRefsRepo); |
| |
| ServletContextHandler app = noRefServer.addContext("/git"); |
| GitServlet gs = new GitServlet(); |
| gs.setRepositoryResolver(new TestRepoResolver(repo, repoName)); |
| app.addServlet(new ServletHolder(gs), "/*"); |
| noRefServer.setUp(); |
| |
| RevBlob A2_txt = repo.blob("A2"); |
| RevCommit A2 = repo.commit().add("A2_txt", A2_txt).create(); |
| RevCommit B2 = repo.commit().parent(A2).add("A2_txt", "C2") |
| .add("B2", "B2").create(); |
| repo.update(master, B2); |
| |
| URIish badRefsURI = new URIish(noRefServer.getURI() |
| .resolve(app.getContextPath() + "/" + repoName).toString()); |
| |
| Repository dst = createBareRepository(); |
| try (Transport t = Transport.open(dst, badRefsURI); |
| FetchConnection c = t.openFetch()) { |
| // We start failing here to exercise the post-advertisement |
| // upload pack handler. |
| badRefsRepo.startFailing(); |
| // Need to flush caches because ref advertisement populated them. |
| badRefsRepo.getRefDatabase().refresh(); |
| c.fetch(NullProgressMonitor.INSTANCE, |
| Collections.singleton(c.getRef(master)), |
| Collections.<ObjectId> emptySet()); |
| fail("Successfully served ref with value " + c.getRef(master)); |
| } catch (TransportException err) { |
| assertEquals("internal server error", err.getMessage()); |
| } |
| } finally { |
| noRefServer.tearDown(); |
| } |
| } |
| |
| @Test |
| public void testPush_NotAuthorized() throws Exception { |
| final TestRepository src = createTestRepository(); |
| final RevBlob Q_txt = src.blob("new text"); |
| final RevCommit Q = src.commit().add("Q", Q_txt).create(); |
| final Repository db = src.getRepository(); |
| final String dstName = Constants.R_HEADS + "new.branch"; |
| |
| // push anonymous shouldn't be allowed. |
| // |
| try (Transport t = Transport.open(db, remoteURI)) { |
| final String srcExpr = Q.name(); |
| final boolean forceUpdate = false; |
| final String localName = null; |
| final ObjectId oldId = null; |
| |
| RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(), |
| srcExpr, dstName, forceUpdate, localName, oldId); |
| try { |
| t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u)); |
| fail("anonymous push incorrectly accepted without error"); |
| } catch (TransportException e) { |
| final String exp = remoteURI + ": " |
| + JGitText.get().authenticationNotSupported; |
| assertEquals(exp, e.getMessage()); |
| } |
| } |
| |
| List<AccessEvent> requests = getRequests(); |
| assertEquals(1, requests.size()); |
| |
| AccessEvent info = requests.get(0); |
| assertEquals("GET", info.getMethod()); |
| assertEquals(join(remoteURI, "info/refs"), info.getPath()); |
| assertEquals(1, info.getParameters().size()); |
| assertEquals("git-receive-pack", info.getParameter("service")); |
| assertEquals(401, info.getStatus()); |
| } |
| |
| @Test |
| public void testPush_CreateBranch() throws Exception { |
| final TestRepository src = createTestRepository(); |
| final RevBlob Q_txt = src.blob("new text"); |
| final RevCommit Q = src.commit().add("Q", Q_txt).create(); |
| final Repository db = src.getRepository(); |
| final String dstName = Constants.R_HEADS + "new.branch"; |
| |
| enableReceivePack(); |
| |
| try (Transport t = Transport.open(db, remoteURI)) { |
| final String srcExpr = Q.name(); |
| final boolean forceUpdate = false; |
| final String localName = null; |
| final ObjectId oldId = null; |
| |
| RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(), |
| srcExpr, dstName, forceUpdate, localName, oldId); |
| t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u)); |
| } |
| |
| assertTrue(remoteRepository.hasObject(Q_txt)); |
| assertNotNull("has " + dstName, remoteRepository.exactRef(dstName)); |
| assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId()); |
| fsck(remoteRepository, Q); |
| |
| final ReflogReader log = remoteRepository.getReflogReader(dstName); |
| assertNotNull("has log for " + dstName, log); |
| |
| final ReflogEntry last = log.getLastEntry(); |
| assertNotNull("has last entry", last); |
| assertEquals(ObjectId.zeroId(), last.getOldId()); |
| assertEquals(Q, last.getNewId()); |
| assertEquals("anonymous", last.getWho().getName()); |
| |
| // Assumption: The host name we use to contact the server should |
| // be the server's own host name, because it should be the loopback |
| // network interface. |
| // |
| final String clientHost = remoteURI.getHost(); |
| assertEquals("anonymous@" + clientHost, last.getWho().getEmailAddress()); |
| assertEquals("push: created", last.getComment()); |
| |
| List<AccessEvent> requests = getRequests(); |
| assertEquals(2, requests.size()); |
| |
| AccessEvent info = requests.get(0); |
| assertEquals("GET", info.getMethod()); |
| assertEquals(join(remoteURI, "info/refs"), info.getPath()); |
| assertEquals(1, info.getParameters().size()); |
| assertEquals("git-receive-pack", info.getParameter("service")); |
| assertEquals(200, info.getStatus()); |
| assertEquals("application/x-git-receive-pack-advertisement", info |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| |
| AccessEvent service = requests.get(1); |
| assertEquals("POST", service.getMethod()); |
| assertEquals(join(remoteURI, "git-receive-pack"), service.getPath()); |
| assertEquals(0, service.getParameters().size()); |
| assertNotNull("has content-length", service |
| .getRequestHeader(HDR_CONTENT_LENGTH)); |
| assertNull("not chunked", service |
| .getRequestHeader(HDR_TRANSFER_ENCODING)); |
| |
| assertEquals(200, service.getStatus()); |
| assertEquals("application/x-git-receive-pack-result", service |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| } |
| |
| @Test |
| public void testPush_ChunkedEncoding() throws Exception { |
| final TestRepository<Repository> src = createTestRepository(); |
| final RevBlob Q_bin = src.blob(new TestRng("Q").nextBytes(128 * 1024)); |
| final RevCommit Q = src.commit().add("Q", Q_bin).create(); |
| final Repository db = src.getRepository(); |
| final String dstName = Constants.R_HEADS + "new.branch"; |
| |
| enableReceivePack(); |
| |
| final StoredConfig cfg = db.getConfig(); |
| cfg.setInt("core", null, "compression", 0); |
| cfg.setInt("http", null, "postbuffer", 8 * 1024); |
| cfg.save(); |
| |
| try (Transport t = Transport.open(db, remoteURI)) { |
| final String srcExpr = Q.name(); |
| final boolean forceUpdate = false; |
| final String localName = null; |
| final ObjectId oldId = null; |
| |
| RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(), |
| srcExpr, dstName, forceUpdate, localName, oldId); |
| t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u)); |
| } |
| |
| assertTrue(remoteRepository.hasObject(Q_bin)); |
| assertNotNull("has " + dstName, remoteRepository.exactRef(dstName)); |
| assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId()); |
| fsck(remoteRepository, Q); |
| |
| List<AccessEvent> requests = getRequests(); |
| assertEquals(2, requests.size()); |
| |
| AccessEvent info = requests.get(0); |
| assertEquals("GET", info.getMethod()); |
| assertEquals(join(remoteURI, "info/refs"), info.getPath()); |
| assertEquals(1, info.getParameters().size()); |
| assertEquals("git-receive-pack", info.getParameter("service")); |
| assertEquals(200, info.getStatus()); |
| assertEquals("application/x-git-receive-pack-advertisement", info |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| |
| AccessEvent service = requests.get(1); |
| assertEquals("POST", service.getMethod()); |
| assertEquals(join(remoteURI, "git-receive-pack"), service.getPath()); |
| assertEquals(0, service.getParameters().size()); |
| assertNull("no content-length", service |
| .getRequestHeader(HDR_CONTENT_LENGTH)); |
| assertEquals("chunked", service.getRequestHeader(HDR_TRANSFER_ENCODING)); |
| |
| assertEquals(200, service.getStatus()); |
| assertEquals("application/x-git-receive-pack-result", service |
| .getResponseHeader(HDR_CONTENT_TYPE)); |
| } |
| |
| private void enableReceivePack() throws IOException { |
| final StoredConfig cfg = remoteRepository.getConfig(); |
| cfg.setBoolean("http", null, "receivepack", true); |
| cfg.save(); |
| } |
| |
| private final class TestRepoResolver |
| implements RepositoryResolver<HttpServletRequest> { |
| |
| private final TestRepository<Repository> repo; |
| |
| private final String repoName; |
| |
| private TestRepoResolver(TestRepository<Repository> repo, |
| String repoName) { |
| this.repo = repo; |
| this.repoName = repoName; |
| } |
| |
| @Override |
| public Repository open(HttpServletRequest req, String name) |
| throws RepositoryNotFoundException, ServiceNotEnabledException { |
| if (!name.equals(repoName)) |
| throw new RepositoryNotFoundException(name); |
| |
| Repository db = repo.getRepository(); |
| db.incrementOpen(); |
| return db; |
| } |
| } |
| } |