View Javadoc
1   /*
2    * Copyright (C) 2010, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.http.test;
45  
46  import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
47  import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH;
48  import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
49  import static org.junit.Assert.assertEquals;
50  import static org.junit.Assert.assertFalse;
51  import static org.junit.Assert.assertNotNull;
52  import static org.junit.Assert.assertNull;
53  import static org.junit.Assert.assertTrue;
54  import static org.junit.Assert.fail;
55  
56  import java.io.IOException;
57  import java.io.PrintWriter;
58  import java.net.URISyntaxException;
59  import java.nio.charset.StandardCharsets;
60  import java.util.Arrays;
61  import java.util.Collection;
62  import java.util.Collections;
63  import java.util.EnumSet;
64  import java.util.List;
65  import java.util.Map;
66  
67  import javax.servlet.DispatcherType;
68  import javax.servlet.Filter;
69  import javax.servlet.FilterChain;
70  import javax.servlet.FilterConfig;
71  import javax.servlet.ServletException;
72  import javax.servlet.ServletRequest;
73  import javax.servlet.ServletResponse;
74  import javax.servlet.http.HttpServletRequest;
75  import javax.servlet.http.HttpServletResponse;
76  
77  import org.eclipse.jetty.servlet.FilterHolder;
78  import org.eclipse.jetty.servlet.ServletContextHandler;
79  import org.eclipse.jetty.servlet.ServletHolder;
80  import org.eclipse.jgit.errors.RemoteRepositoryException;
81  import org.eclipse.jgit.errors.RepositoryNotFoundException;
82  import org.eclipse.jgit.errors.TransportException;
83  import org.eclipse.jgit.http.server.GitServlet;
84  import org.eclipse.jgit.internal.JGitText;
85  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
86  import org.eclipse.jgit.junit.TestRepository;
87  import org.eclipse.jgit.junit.TestRng;
88  import org.eclipse.jgit.junit.http.AccessEvent;
89  import org.eclipse.jgit.junit.http.AppServer;
90  import org.eclipse.jgit.junit.http.HttpTestCase;
91  import org.eclipse.jgit.lib.ConfigConstants;
92  import org.eclipse.jgit.lib.Constants;
93  import org.eclipse.jgit.lib.NullProgressMonitor;
94  import org.eclipse.jgit.lib.ObjectId;
95  import org.eclipse.jgit.lib.ObjectIdRef;
96  import org.eclipse.jgit.lib.ObjectInserter;
97  import org.eclipse.jgit.lib.Ref;
98  import org.eclipse.jgit.lib.ReflogEntry;
99  import org.eclipse.jgit.lib.ReflogReader;
100 import org.eclipse.jgit.lib.Repository;
101 import org.eclipse.jgit.lib.StoredConfig;
102 import org.eclipse.jgit.revwalk.RevBlob;
103 import org.eclipse.jgit.revwalk.RevCommit;
104 import org.eclipse.jgit.transport.FetchConnection;
105 import org.eclipse.jgit.transport.HttpTransport;
106 import org.eclipse.jgit.transport.RemoteRefUpdate;
107 import org.eclipse.jgit.transport.Transport;
108 import org.eclipse.jgit.transport.TransportHttp;
109 import org.eclipse.jgit.transport.URIish;
110 import org.eclipse.jgit.transport.http.HttpConnectionFactory;
111 import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
112 import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
113 import org.eclipse.jgit.transport.resolver.RepositoryResolver;
114 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
115 import org.eclipse.jgit.util.HttpSupport;
116 import org.junit.Before;
117 import org.junit.Test;
118 import org.junit.runner.RunWith;
119 import org.junit.runners.Parameterized;
120 import org.junit.runners.Parameterized.Parameters;
121 
122 @RunWith(Parameterized.class)
123 public class SmartClientSmartServerTest extends HttpTestCase {
124 	private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding";
125 
126 	private Repository remoteRepository;
127 
128 	private URIish remoteURI;
129 
130 	private URIish brokenURI;
131 
132 	private URIish redirectURI;
133 
134 	private RevBlob A_txt;
135 
136 	private RevCommit A, B;
137 
138 	@Parameters
139 	public static Collection<Object[]> data() {
140 		// run all tests with both connection factories we have
141 		return Arrays.asList(new Object[][] {
142 				{ new JDKHttpConnectionFactory() },
143 				{ new HttpClientConnectionFactory() } });
144 	}
145 
146 	public SmartClientSmartServerTest(HttpConnectionFactory cf) {
147 		HttpTransport.setConnectionFactory(cf);
148 	}
149 
150 	@Override
151 	@Before
152 	public void setUp() throws Exception {
153 		super.setUp();
154 
155 		final TestRepository<Repository> src = createTestRepository();
156 		final String srcName = src.getRepository().getDirectory().getName();
157 		src.getRepository()
158 				.getConfig()
159 				.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
160 						ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
161 
162 		GitServlet gs = new GitServlet();
163 
164 		ServletContextHandler app = addNormalContext(gs, src, srcName);
165 
166 		ServletContextHandler broken = addBrokenContext(gs, src, srcName);
167 
168 		ServletContextHandler redirect = addRedirectContext(gs, src, srcName);
169 
170 		server.setUp();
171 
172 		remoteRepository = src.getRepository();
173 		remoteURI = toURIish(app, srcName);
174 		brokenURI = toURIish(broken, srcName);
175 		redirectURI = toURIish(redirect, srcName);
176 
177 		A_txt = src.blob("A");
178 		A = src.commit().add("A_txt", A_txt).create();
179 		B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create();
180 		src.update(master, B);
181 
182 		src.update("refs/garbage/a/very/long/ref/name/to/compress", B);
183 	}
184 
185 	private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
186 		ServletContextHandler app = server.addContext("/git");
187 		gs.setRepositoryResolver(new TestRepoResolver(src, srcName));
188 		app.addServlet(new ServletHolder(gs), "/*");
189 		return app;
190 	}
191 
192 	@SuppressWarnings("unused")
193 	private ServletContextHandler addBrokenContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
194 		ServletContextHandler broken = server.addContext("/bad");
195 		broken.addFilter(new FilterHolder(new Filter() {
196 
197 			@Override
198 			public void doFilter(ServletRequest request,
199 					ServletResponse response, FilterChain chain)
200 					throws IOException, ServletException {
201 				final HttpServletResponse r = (HttpServletResponse) response;
202 				r.setContentType("text/plain");
203 				r.setCharacterEncoding("UTF-8");
204 				PrintWriter w = r.getWriter();
205 				w.print("OK");
206 				w.close();
207 			}
208 
209 			@Override
210 			public void init(FilterConfig filterConfig)
211 					throws ServletException {
212 				// empty
213 			}
214 
215 			@Override
216 			public void destroy() {
217 				// empty
218 			}
219 		}), "/" + srcName + "/git-upload-pack",
220 				EnumSet.of(DispatcherType.REQUEST));
221 		broken.addServlet(new ServletHolder(gs), "/*");
222 		return broken;
223 	}
224 
225 	@SuppressWarnings("unused")
226 	private ServletContextHandler addRedirectContext(GitServlet gs,
227 			TestRepository<Repository> src, String srcName) {
228 		ServletContextHandler redirect = server.addContext("/redirect");
229 		redirect.addFilter(new FilterHolder(new Filter() {
230 
231 			@Override
232 			public void init(FilterConfig filterConfig)
233 					throws ServletException {
234 				// empty
235 			}
236 
237 			@Override
238 			public void doFilter(ServletRequest request,
239 					ServletResponse response, FilterChain chain)
240 					throws IOException, ServletException {
241 				final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
242 				final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
243 				final StringBuffer fullUrl = httpServletRequest.getRequestURL();
244 				if (httpServletRequest.getQueryString() != null) {
245 					fullUrl.append("?")
246 							.append(httpServletRequest.getQueryString());
247 				}
248 				httpServletResponse
249 						.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
250 				httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
251 						fullUrl.toString().replace("/redirect", "/git"));
252 			}
253 
254 			@Override
255 			public void destroy() {
256 				// empty
257 			}
258 		}), "/*", EnumSet.of(DispatcherType.REQUEST));
259 		redirect.addServlet(new ServletHolder(gs), "/*");
260 		return redirect;
261 	}
262 
263 	@Test
264 	public void testListRemote() throws IOException {
265 		Repository dst = createBareRepository();
266 
267 		assertEquals("http", remoteURI.getScheme());
268 
269 		Map<String, Ref> map;
270 		try (Transport t = Transport.open(dst, remoteURI)) {
271 			// I didn't make up these public interface names, I just
272 			// approved them for inclusion into the code base. Sorry.
273 			// --spearce
274 			//
275 			assertTrue("isa TransportHttp", t instanceof TransportHttp);
276 			assertTrue("isa HttpTransport", t instanceof HttpTransport);
277 
278 			FetchConnection c = t.openFetch();
279 			try {
280 				map = c.getRefsMap();
281 			} finally {
282 				c.close();
283 			}
284 		}
285 
286 		assertNotNull("have map of refs", map);
287 		assertEquals(3, map.size());
288 
289 		assertNotNull("has " + master, map.get(master));
290 		assertEquals(B, map.get(master).getObjectId());
291 
292 		assertNotNull("has " + Constants.HEAD, map.get(Constants.HEAD));
293 		assertEquals(B, map.get(Constants.HEAD).getObjectId());
294 
295 		List<AccessEvent> requests = getRequests();
296 		assertEquals(1, requests.size());
297 
298 		AccessEvent info = requests.get(0);
299 		assertEquals("GET", info.getMethod());
300 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
301 		assertEquals(1, info.getParameters().size());
302 		assertEquals("git-upload-pack", info.getParameter("service"));
303 		assertEquals(200, info.getStatus());
304 		assertEquals("application/x-git-upload-pack-advertisement", info
305 				.getResponseHeader(HDR_CONTENT_TYPE));
306 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
307 	}
308 
309 	@Test
310 	public void testListRemote_BadName() throws IOException, URISyntaxException {
311 		Repository dst = createBareRepository();
312 		URIish uri = new URIish(this.remoteURI.toString() + ".invalid");
313 		try (Transport t = Transport.open(dst, uri)) {
314 			try {
315 				t.openFetch();
316 				fail("fetch connection opened");
317 			} catch (RemoteRepositoryException notFound) {
318 				assertEquals(uri + ": Git repository not found",
319 						notFound.getMessage());
320 			}
321 		}
322 
323 		List<AccessEvent> requests = getRequests();
324 		assertEquals(1, requests.size());
325 
326 		AccessEvent info = requests.get(0);
327 		assertEquals("GET", info.getMethod());
328 		assertEquals(join(uri, "info/refs"), info.getPath());
329 		assertEquals(1, info.getParameters().size());
330 		assertEquals("git-upload-pack", info.getParameter("service"));
331 		assertEquals(200, info.getStatus());
332 		assertEquals("application/x-git-upload-pack-advertisement",
333 				info.getResponseHeader(HDR_CONTENT_TYPE));
334 	}
335 
336 	@Test
337 	public void testInitialClone_Small() throws Exception {
338 		Repository dst = createBareRepository();
339 		assertFalse(dst.hasObject(A_txt));
340 
341 		try (Transport t = Transport.open(dst, remoteURI)) {
342 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
343 		}
344 
345 		assertTrue(dst.hasObject(A_txt));
346 		assertEquals(B, dst.exactRef(master).getObjectId());
347 		fsck(dst, B);
348 
349 		List<AccessEvent> requests = getRequests();
350 		assertEquals(2, requests.size());
351 
352 		AccessEvent info = requests.get(0);
353 		assertEquals("GET", info.getMethod());
354 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
355 		assertEquals(1, info.getParameters().size());
356 		assertEquals("git-upload-pack", info.getParameter("service"));
357 		assertEquals(200, info.getStatus());
358 		assertEquals("application/x-git-upload-pack-advertisement", info
359 				.getResponseHeader(HDR_CONTENT_TYPE));
360 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
361 
362 		AccessEvent service = requests.get(1);
363 		assertEquals("POST", service.getMethod());
364 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
365 		assertEquals(0, service.getParameters().size());
366 		assertNotNull("has content-length", service
367 				.getRequestHeader(HDR_CONTENT_LENGTH));
368 		assertNull("not chunked", service
369 				.getRequestHeader(HDR_TRANSFER_ENCODING));
370 
371 		assertEquals(200, service.getStatus());
372 		assertEquals("application/x-git-upload-pack-result", service
373 				.getResponseHeader(HDR_CONTENT_TYPE));
374 	}
375 
376 	@Test
377 	public void testInitialClone_RedirectSmall() throws Exception {
378 		Repository dst = createBareRepository();
379 		assertFalse(dst.hasObject(A_txt));
380 
381 		try (Transport t = Transport.open(dst, redirectURI)) {
382 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
383 		}
384 
385 		assertTrue(dst.hasObject(A_txt));
386 		assertEquals(B, dst.exactRef(master).getObjectId());
387 		fsck(dst, B);
388 
389 		List<AccessEvent> requests = getRequests();
390 		assertEquals(4, requests.size());
391 
392 		AccessEvent firstRedirect = requests.get(0);
393 		assertEquals(301, firstRedirect.getStatus());
394 
395 		AccessEvent info = requests.get(1);
396 		assertEquals("GET", info.getMethod());
397 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
398 		assertEquals(1, info.getParameters().size());
399 		assertEquals("git-upload-pack", info.getParameter("service"));
400 		assertEquals(200, info.getStatus());
401 		assertEquals("application/x-git-upload-pack-advertisement",
402 				info.getResponseHeader(HDR_CONTENT_TYPE));
403 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
404 
405 		AccessEvent secondRedirect = requests.get(2);
406 		assertEquals(301, secondRedirect.getStatus());
407 
408 		AccessEvent service = requests.get(3);
409 		assertEquals("POST", service.getMethod());
410 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
411 		assertEquals(0, service.getParameters().size());
412 		assertNotNull("has content-length",
413 				service.getRequestHeader(HDR_CONTENT_LENGTH));
414 		assertNull("not chunked",
415 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
416 
417 		assertEquals(200, service.getStatus());
418 		assertEquals("application/x-git-upload-pack-result",
419 				service.getResponseHeader(HDR_CONTENT_TYPE));
420 	}
421 
422 	@Test
423 	public void testFetch_FewLocalCommits() throws Exception {
424 		// Bootstrap by doing the clone.
425 		//
426 		TestRepository dst = createTestRepository();
427 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
428 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
429 		}
430 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
431 		List<AccessEvent> cloneRequests = getRequests();
432 
433 		// Only create a few new commits.
434 		TestRepository.BranchBuilder b = dst.branch(master);
435 		for (int i = 0; i < 4; i++)
436 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
437 
438 		// Create a new commit on the remote.
439 		//
440 		b = new TestRepository<>(remoteRepository).branch(master);
441 		RevCommit Z = b.commit().message("Z").create();
442 
443 		// Now incrementally update.
444 		//
445 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
446 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
447 		}
448 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
449 
450 		List<AccessEvent> requests = getRequests();
451 		requests.removeAll(cloneRequests);
452 		assertEquals(2, requests.size());
453 
454 		AccessEvent info = requests.get(0);
455 		assertEquals("GET", info.getMethod());
456 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
457 		assertEquals(1, info.getParameters().size());
458 		assertEquals("git-upload-pack", info.getParameter("service"));
459 		assertEquals(200, info.getStatus());
460 		assertEquals("application/x-git-upload-pack-advertisement",
461 				info.getResponseHeader(HDR_CONTENT_TYPE));
462 
463 		// We should have needed one request to perform the fetch.
464 		//
465 		AccessEvent service = requests.get(1);
466 		assertEquals("POST", service.getMethod());
467 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
468 		assertEquals(0, service.getParameters().size());
469 		assertNotNull("has content-length",
470 				service.getRequestHeader(HDR_CONTENT_LENGTH));
471 		assertNull("not chunked",
472 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
473 
474 		assertEquals(200, service.getStatus());
475 		assertEquals("application/x-git-upload-pack-result",
476 				service.getResponseHeader(HDR_CONTENT_TYPE));
477 	}
478 
479 	@Test
480 	public void testFetch_TooManyLocalCommits() throws Exception {
481 		// Bootstrap by doing the clone.
482 		//
483 		TestRepository dst = createTestRepository();
484 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
485 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
486 		}
487 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
488 		List<AccessEvent> cloneRequests = getRequests();
489 
490 		// Force enough into the local client that enumeration will
491 		// need multiple packets, but not too many to overflow and
492 		// not pick up the ACK_COMMON message.
493 		//
494 		TestRepository.BranchBuilder b = dst.branch(master);
495 		for (int i = 0; i < 32 - 1; i++)
496 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
497 
498 		// Create a new commit on the remote.
499 		//
500 		b = new TestRepository<>(remoteRepository).branch(master);
501 		RevCommit Z = b.commit().message("Z").create();
502 
503 		// Now incrementally update.
504 		//
505 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
506 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
507 		}
508 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
509 
510 		List<AccessEvent> requests = getRequests();
511 		requests.removeAll(cloneRequests);
512 		assertEquals(3, requests.size());
513 
514 		AccessEvent info = requests.get(0);
515 		assertEquals("GET", info.getMethod());
516 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
517 		assertEquals(1, info.getParameters().size());
518 		assertEquals("git-upload-pack", info.getParameter("service"));
519 		assertEquals(200, info.getStatus());
520 		assertEquals("application/x-git-upload-pack-advertisement", info
521 				.getResponseHeader(HDR_CONTENT_TYPE));
522 
523 		// We should have needed two requests to perform the fetch
524 		// due to the high number of local unknown commits.
525 		//
526 		AccessEvent service = requests.get(1);
527 		assertEquals("POST", service.getMethod());
528 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
529 		assertEquals(0, service.getParameters().size());
530 		assertNotNull("has content-length", service
531 				.getRequestHeader(HDR_CONTENT_LENGTH));
532 		assertNull("not chunked", service
533 				.getRequestHeader(HDR_TRANSFER_ENCODING));
534 
535 		assertEquals(200, service.getStatus());
536 		assertEquals("application/x-git-upload-pack-result", service
537 				.getResponseHeader(HDR_CONTENT_TYPE));
538 
539 		service = requests.get(2);
540 		assertEquals("POST", service.getMethod());
541 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
542 		assertEquals(0, service.getParameters().size());
543 		assertNotNull("has content-length", service
544 				.getRequestHeader(HDR_CONTENT_LENGTH));
545 		assertNull("not chunked", service
546 				.getRequestHeader(HDR_TRANSFER_ENCODING));
547 
548 		assertEquals(200, service.getStatus());
549 		assertEquals("application/x-git-upload-pack-result", service
550 				.getResponseHeader(HDR_CONTENT_TYPE));
551 	}
552 
553 	@Test
554 	public void testInitialClone_BrokenServer() throws Exception {
555 		Repository dst = createBareRepository();
556 		assertFalse(dst.hasObject(A_txt));
557 
558 		try (Transport t = Transport.open(dst, brokenURI)) {
559 			try {
560 				t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
561 				fail("fetch completed despite upload-pack being broken");
562 			} catch (TransportException err) {
563 				String exp = brokenURI + ": expected"
564 						+ " Content-Type application/x-git-upload-pack-result;"
565 						+ " received Content-Type text/plain;charset=utf-8";
566 				assertEquals(exp, err.getMessage());
567 			}
568 		}
569 
570 		List<AccessEvent> requests = getRequests();
571 		assertEquals(2, requests.size());
572 
573 		AccessEvent info = requests.get(0);
574 		assertEquals("GET", info.getMethod());
575 		assertEquals(join(brokenURI, "info/refs"), info.getPath());
576 		assertEquals(1, info.getParameters().size());
577 		assertEquals("git-upload-pack", info.getParameter("service"));
578 		assertEquals(200, info.getStatus());
579 		assertEquals("application/x-git-upload-pack-advertisement", info
580 				.getResponseHeader(HDR_CONTENT_TYPE));
581 
582 		AccessEvent service = requests.get(1);
583 		assertEquals("POST", service.getMethod());
584 		assertEquals(join(brokenURI, "git-upload-pack"), service.getPath());
585 		assertEquals(0, service.getParameters().size());
586 		assertEquals(200, service.getStatus());
587 		assertEquals("text/plain;charset=utf-8",
588 				service.getResponseHeader(HDR_CONTENT_TYPE));
589 	}
590 
591 	@Test
592 	public void testInvalidWant() throws Exception {
593 		@SuppressWarnings("resource")
594 		ObjectId id = new ObjectInserter.Formatter().idFor(Constants.OBJ_BLOB,
595 				"testInvalidWant".getBytes(StandardCharsets.UTF_8));
596 
597 		Repository dst = createBareRepository();
598 		try (Transport t = Transport.open(dst, remoteURI);
599 				FetchConnection c = t.openFetch()) {
600 			Ref want = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(),
601 					id);
602 			c.fetch(NullProgressMonitor.INSTANCE, Collections.singleton(want),
603 					Collections.<ObjectId> emptySet());
604 			fail("Server accepted want " + id.name());
605 		} catch (TransportException err) {
606 			assertEquals("want " + id.name() + " not valid", err.getMessage());
607 		}
608 	}
609 
610 	@Test
611 	public void testFetch_RefsUnreadableOnUpload() throws Exception {
612 		AppServer noRefServer = new AppServer();
613 		try {
614 			final String repoName = "refs-unreadable";
615 			RefsUnreadableInMemoryRepository badRefsRepo = new RefsUnreadableInMemoryRepository(
616 					new DfsRepositoryDescription(repoName));
617 			final TestRepository<Repository> repo = new TestRepository<>(
618 					badRefsRepo);
619 
620 			ServletContextHandler app = noRefServer.addContext("/git");
621 			GitServlet gs = new GitServlet();
622 			gs.setRepositoryResolver(new TestRepoResolver(repo, repoName));
623 			app.addServlet(new ServletHolder(gs), "/*");
624 			noRefServer.setUp();
625 
626 			RevBlob A2_txt = repo.blob("A2");
627 			RevCommit A2 = repo.commit().add("A2_txt", A2_txt).create();
628 			RevCommit B2 = repo.commit().parent(A2).add("A2_txt", "C2")
629 					.add("B2", "B2").create();
630 			repo.update(master, B2);
631 
632 			URIish badRefsURI = new URIish(noRefServer.getURI()
633 					.resolve(app.getContextPath() + "/" + repoName).toString());
634 
635 			Repository dst = createBareRepository();
636 			try (Transport t = Transport.open(dst, badRefsURI);
637 					FetchConnection c = t.openFetch()) {
638 				// We start failing here to exercise the post-advertisement
639 				// upload pack handler.
640 				badRefsRepo.startFailing();
641 				// Need to flush caches because ref advertisement populated them.
642 				badRefsRepo.getRefDatabase().refresh();
643 				c.fetch(NullProgressMonitor.INSTANCE,
644 						Collections.singleton(c.getRef(master)),
645 						Collections.<ObjectId> emptySet());
646 				fail("Successfully served ref with value " + c.getRef(master));
647 			} catch (TransportException err) {
648 				assertEquals("internal server error", err.getMessage());
649 			}
650 		} finally {
651 			noRefServer.tearDown();
652 		}
653 	}
654 
655 	@Test
656 	public void testPush_NotAuthorized() throws Exception {
657 		final TestRepository src = createTestRepository();
658 		final RevBlob Q_txt = src.blob("new text");
659 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
660 		final Repository db = src.getRepository();
661 		final String dstName = Constants.R_HEADS + "new.branch";
662 
663 		// push anonymous shouldn't be allowed.
664 		//
665 		try (Transport t = Transport.open(db, remoteURI)) {
666 			final String srcExpr = Q.name();
667 			final boolean forceUpdate = false;
668 			final String localName = null;
669 			final ObjectId oldId = null;
670 
671 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
672 					srcExpr, dstName, forceUpdate, localName, oldId);
673 			try {
674 				t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
675 				fail("anonymous push incorrectly accepted without error");
676 			} catch (TransportException e) {
677 				final String exp = remoteURI + ": "
678 						+ JGitText.get().authenticationNotSupported;
679 				assertEquals(exp, e.getMessage());
680 			}
681 		}
682 
683 		List<AccessEvent> requests = getRequests();
684 		assertEquals(1, requests.size());
685 
686 		AccessEvent info = requests.get(0);
687 		assertEquals("GET", info.getMethod());
688 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
689 		assertEquals(1, info.getParameters().size());
690 		assertEquals("git-receive-pack", info.getParameter("service"));
691 		assertEquals(401, info.getStatus());
692 	}
693 
694 	@Test
695 	public void testPush_CreateBranch() throws Exception {
696 		final TestRepository src = createTestRepository();
697 		final RevBlob Q_txt = src.blob("new text");
698 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
699 		final Repository db = src.getRepository();
700 		final String dstName = Constants.R_HEADS + "new.branch";
701 
702 		enableReceivePack();
703 
704 		try (Transport t = Transport.open(db, remoteURI)) {
705 			final String srcExpr = Q.name();
706 			final boolean forceUpdate = false;
707 			final String localName = null;
708 			final ObjectId oldId = null;
709 
710 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
711 					srcExpr, dstName, forceUpdate, localName, oldId);
712 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
713 		}
714 
715 		assertTrue(remoteRepository.hasObject(Q_txt));
716 		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
717 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
718 		fsck(remoteRepository, Q);
719 
720 		final ReflogReader log = remoteRepository.getReflogReader(dstName);
721 		assertNotNull("has log for " + dstName, log);
722 
723 		final ReflogEntry last = log.getLastEntry();
724 		assertNotNull("has last entry", last);
725 		assertEquals(ObjectId.zeroId(), last.getOldId());
726 		assertEquals(Q, last.getNewId());
727 		assertEquals("anonymous", last.getWho().getName());
728 
729 		// Assumption: The host name we use to contact the server should
730 		// be the server's own host name, because it should be the loopback
731 		// network interface.
732 		//
733 		final String clientHost = remoteURI.getHost();
734 		assertEquals("anonymous@" + clientHost, last.getWho().getEmailAddress());
735 		assertEquals("push: created", last.getComment());
736 
737 		List<AccessEvent> requests = getRequests();
738 		assertEquals(2, requests.size());
739 
740 		AccessEvent info = requests.get(0);
741 		assertEquals("GET", info.getMethod());
742 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
743 		assertEquals(1, info.getParameters().size());
744 		assertEquals("git-receive-pack", info.getParameter("service"));
745 		assertEquals(200, info.getStatus());
746 		assertEquals("application/x-git-receive-pack-advertisement", info
747 				.getResponseHeader(HDR_CONTENT_TYPE));
748 
749 		AccessEvent service = requests.get(1);
750 		assertEquals("POST", service.getMethod());
751 		assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
752 		assertEquals(0, service.getParameters().size());
753 		assertNotNull("has content-length", service
754 				.getRequestHeader(HDR_CONTENT_LENGTH));
755 		assertNull("not chunked", service
756 				.getRequestHeader(HDR_TRANSFER_ENCODING));
757 
758 		assertEquals(200, service.getStatus());
759 		assertEquals("application/x-git-receive-pack-result", service
760 				.getResponseHeader(HDR_CONTENT_TYPE));
761 	}
762 
763 	@Test
764 	public void testPush_ChunkedEncoding() throws Exception {
765 		final TestRepository<Repository> src = createTestRepository();
766 		final RevBlob Q_bin = src.blob(new TestRng("Q").nextBytes(128 * 1024));
767 		final RevCommit Q = src.commit().add("Q", Q_bin).create();
768 		final Repository db = src.getRepository();
769 		final String dstName = Constants.R_HEADS + "new.branch";
770 
771 		enableReceivePack();
772 
773 		final StoredConfig cfg = db.getConfig();
774 		cfg.setInt("core", null, "compression", 0);
775 		cfg.setInt("http", null, "postbuffer", 8 * 1024);
776 		cfg.save();
777 
778 		try (Transport t = Transport.open(db, remoteURI)) {
779 			final String srcExpr = Q.name();
780 			final boolean forceUpdate = false;
781 			final String localName = null;
782 			final ObjectId oldId = null;
783 
784 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
785 					srcExpr, dstName, forceUpdate, localName, oldId);
786 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
787 		}
788 
789 		assertTrue(remoteRepository.hasObject(Q_bin));
790 		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
791 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
792 		fsck(remoteRepository, Q);
793 
794 		List<AccessEvent> requests = getRequests();
795 		assertEquals(2, requests.size());
796 
797 		AccessEvent info = requests.get(0);
798 		assertEquals("GET", info.getMethod());
799 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
800 		assertEquals(1, info.getParameters().size());
801 		assertEquals("git-receive-pack", info.getParameter("service"));
802 		assertEquals(200, info.getStatus());
803 		assertEquals("application/x-git-receive-pack-advertisement", info
804 				.getResponseHeader(HDR_CONTENT_TYPE));
805 
806 		AccessEvent service = requests.get(1);
807 		assertEquals("POST", service.getMethod());
808 		assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
809 		assertEquals(0, service.getParameters().size());
810 		assertNull("no content-length", service
811 				.getRequestHeader(HDR_CONTENT_LENGTH));
812 		assertEquals("chunked", service.getRequestHeader(HDR_TRANSFER_ENCODING));
813 
814 		assertEquals(200, service.getStatus());
815 		assertEquals("application/x-git-receive-pack-result", service
816 				.getResponseHeader(HDR_CONTENT_TYPE));
817 	}
818 
819 	private void enableReceivePack() throws IOException {
820 		final StoredConfig cfg = remoteRepository.getConfig();
821 		cfg.setBoolean("http", null, "receivepack", true);
822 		cfg.save();
823 	}
824 
825 	private final class TestRepoResolver
826 			implements RepositoryResolver<HttpServletRequest> {
827 
828 		private final TestRepository<Repository> repo;
829 
830 		private final String repoName;
831 
832 		private TestRepoResolver(TestRepository<Repository> repo,
833 				String repoName) {
834 			this.repo = repo;
835 			this.repoName = repoName;
836 		}
837 
838 		@Override
839 		public Repository open(HttpServletRequest req, String name)
840 				throws RepositoryNotFoundException, ServiceNotEnabledException {
841 			if (!name.equals(repoName))
842 				throw new RepositoryNotFoundException(name);
843 
844 			Repository db = repo.getRepository();
845 			db.incrementOpen();
846 			return db;
847 		}
848 	}
849 }