View Javadoc
1   /*
2    * Copyright (C) 2010, 2017 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.lib.Constants.CHARSET;
47  import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
48  import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH;
49  import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
50  import static org.junit.Assert.assertEquals;
51  import static org.junit.Assert.assertFalse;
52  import static org.junit.Assert.assertNotNull;
53  import static org.junit.Assert.assertNull;
54  import static org.junit.Assert.assertTrue;
55  import static org.junit.Assert.fail;
56  
57  import java.io.IOException;
58  import java.io.PrintWriter;
59  import java.net.URISyntaxException;
60  import java.text.MessageFormat;
61  import java.util.Arrays;
62  import java.util.Collection;
63  import java.util.Collections;
64  import java.util.EnumSet;
65  import java.util.List;
66  import java.util.Map;
67  import java.util.regex.Matcher;
68  import java.util.regex.Pattern;
69  
70  import javax.servlet.DispatcherType;
71  import javax.servlet.Filter;
72  import javax.servlet.FilterChain;
73  import javax.servlet.FilterConfig;
74  import javax.servlet.RequestDispatcher;
75  import javax.servlet.ServletException;
76  import javax.servlet.ServletRequest;
77  import javax.servlet.ServletResponse;
78  import javax.servlet.http.HttpServletRequest;
79  import javax.servlet.http.HttpServletResponse;
80  
81  import org.eclipse.jetty.servlet.FilterHolder;
82  import org.eclipse.jetty.servlet.ServletContextHandler;
83  import org.eclipse.jetty.servlet.ServletHolder;
84  import org.eclipse.jgit.errors.RemoteRepositoryException;
85  import org.eclipse.jgit.errors.TransportException;
86  import org.eclipse.jgit.errors.UnsupportedCredentialItem;
87  import org.eclipse.jgit.http.server.GitServlet;
88  import org.eclipse.jgit.internal.JGitText;
89  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
90  import org.eclipse.jgit.junit.TestRepository;
91  import org.eclipse.jgit.junit.TestRng;
92  import org.eclipse.jgit.junit.http.AccessEvent;
93  import org.eclipse.jgit.junit.http.AppServer;
94  import org.eclipse.jgit.junit.http.HttpTestCase;
95  import org.eclipse.jgit.lib.ConfigConstants;
96  import org.eclipse.jgit.lib.Constants;
97  import org.eclipse.jgit.lib.NullProgressMonitor;
98  import org.eclipse.jgit.lib.ObjectId;
99  import org.eclipse.jgit.lib.ObjectIdRef;
100 import org.eclipse.jgit.lib.ObjectInserter;
101 import org.eclipse.jgit.lib.Ref;
102 import org.eclipse.jgit.lib.ReflogEntry;
103 import org.eclipse.jgit.lib.ReflogReader;
104 import org.eclipse.jgit.lib.Repository;
105 import org.eclipse.jgit.lib.StoredConfig;
106 import org.eclipse.jgit.revwalk.RevBlob;
107 import org.eclipse.jgit.revwalk.RevCommit;
108 import org.eclipse.jgit.storage.file.FileBasedConfig;
109 import org.eclipse.jgit.transport.CredentialItem;
110 import org.eclipse.jgit.transport.CredentialsProvider;
111 import org.eclipse.jgit.transport.FetchConnection;
112 import org.eclipse.jgit.transport.HttpTransport;
113 import org.eclipse.jgit.transport.RemoteRefUpdate;
114 import org.eclipse.jgit.transport.Transport;
115 import org.eclipse.jgit.transport.TransportHttp;
116 import org.eclipse.jgit.transport.URIish;
117 import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
118 import org.eclipse.jgit.transport.http.HttpConnectionFactory;
119 import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
120 import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
121 import org.eclipse.jgit.util.FS;
122 import org.eclipse.jgit.util.HttpSupport;
123 import org.eclipse.jgit.util.SystemReader;
124 import org.junit.Before;
125 import org.junit.Test;
126 import org.junit.runner.RunWith;
127 import org.junit.runners.Parameterized;
128 import org.junit.runners.Parameterized.Parameters;
129 
130 @RunWith(Parameterized.class)
131 public class SmartClientSmartServerTest extends HttpTestCase {
132 	private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding";
133 
134 	private Repository remoteRepository;
135 
136 	private CredentialsProvider testCredentials = new UsernamePasswordCredentialsProvider(
137 			AppServer.username, AppServer.password);
138 
139 	private URIish remoteURI;
140 
141 	private URIish brokenURI;
142 
143 	private URIish redirectURI;
144 
145 	private URIish authURI;
146 
147 	private URIish authOnPostURI;
148 
149 	private RevBlob A_txt;
150 
151 	private RevCommit A, B;
152 
153 	@Parameters
154 	public static Collection<Object[]> data() {
155 		// run all tests with both connection factories we have
156 		return Arrays.asList(new Object[][] {
157 				{ new JDKHttpConnectionFactory() },
158 				{ new HttpClientConnectionFactory() } });
159 	}
160 
161 	public SmartClientSmartServerTest(HttpConnectionFactory cf) {
162 		HttpTransport.setConnectionFactory(cf);
163 	}
164 
165 	@Override
166 	@Before
167 	public void setUp() throws Exception {
168 		super.setUp();
169 
170 		final TestRepository<Repository> src = createTestRepository();
171 		final String srcName = src.getRepository().getDirectory().getName();
172 		src.getRepository()
173 				.getConfig()
174 				.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
175 						ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
176 
177 		GitServlet gs = new GitServlet();
178 
179 		ServletContextHandler app = addNormalContext(gs, src, srcName);
180 
181 		ServletContextHandler broken = addBrokenContext(gs, src, srcName);
182 
183 		ServletContextHandler redirect = addRedirectContext(gs);
184 
185 		ServletContextHandler auth = addAuthContext(gs, "auth");
186 
187 		ServletContextHandler authOnPost = addAuthContext(gs, "pauth", "POST");
188 
189 		server.setUp();
190 
191 		remoteRepository = src.getRepository();
192 		remoteURI = toURIish(app, srcName);
193 		brokenURI = toURIish(broken, srcName);
194 		redirectURI = toURIish(redirect, srcName);
195 		authURI = toURIish(auth, srcName);
196 		authOnPostURI = toURIish(authOnPost, srcName);
197 
198 		A_txt = src.blob("A");
199 		A = src.commit().add("A_txt", A_txt).create();
200 		B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create();
201 		src.update(master, B);
202 
203 		src.update("refs/garbage/a/very/long/ref/name/to/compress", B);
204 	}
205 
206 	private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
207 		ServletContextHandler app = server.addContext("/git");
208 		app.addFilter(new FilterHolder(new Filter() {
209 
210 			@Override
211 			public void init(FilterConfig filterConfig)
212 					throws ServletException {
213 				// empty
214 			}
215 
216 			// Does an internal forward for GET requests containing "/post/",
217 			// and issues a 301 redirect on POST requests for such URLs. Used
218 			// in the POST redirect tests.
219 			@Override
220 			public void doFilter(ServletRequest request,
221 					ServletResponse response, FilterChain chain)
222 					throws IOException, ServletException {
223 				final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
224 				final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
225 				final StringBuffer fullUrl = httpServletRequest.getRequestURL();
226 				if (httpServletRequest.getQueryString() != null) {
227 					fullUrl.append("?")
228 							.append(httpServletRequest.getQueryString());
229 				}
230 				String urlString = fullUrl.toString();
231 				if ("POST".equalsIgnoreCase(httpServletRequest.getMethod())) {
232 					httpServletResponse.setStatus(
233 							HttpServletResponse.SC_MOVED_PERMANENTLY);
234 					httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
235 							urlString.replace("/post/", "/"));
236 				} else {
237 					String path = httpServletRequest.getPathInfo();
238 					path = path.replace("/post/", "/");
239 					if (httpServletRequest.getQueryString() != null) {
240 						path += '?' + httpServletRequest.getQueryString();
241 					}
242 					RequestDispatcher dispatcher = httpServletRequest
243 							.getRequestDispatcher(path);
244 					dispatcher.forward(httpServletRequest, httpServletResponse);
245 				}
246 			}
247 
248 			@Override
249 			public void destroy() {
250 				// empty
251 			}
252 		}), "/post/*", EnumSet.of(DispatcherType.REQUEST));
253 		gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName));
254 		app.addServlet(new ServletHolder(gs), "/*");
255 		return app;
256 	}
257 
258 	@SuppressWarnings("unused")
259 	private ServletContextHandler addBrokenContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
260 		ServletContextHandler broken = server.addContext("/bad");
261 		broken.addFilter(new FilterHolder(new Filter() {
262 
263 			@Override
264 			public void doFilter(ServletRequest request,
265 					ServletResponse response, FilterChain chain)
266 					throws IOException, ServletException {
267 				final HttpServletResponse r = (HttpServletResponse) response;
268 				r.setContentType("text/plain");
269 				r.setCharacterEncoding(Constants.CHARACTER_ENCODING);
270 				try (PrintWriter w = r.getWriter()) {
271 					w.print("OK");
272 				}
273 			}
274 
275 			@Override
276 			public void init(FilterConfig filterConfig)
277 					throws ServletException {
278 				// empty
279 			}
280 
281 			@Override
282 			public void destroy() {
283 				// empty
284 			}
285 		}), "/" + srcName + "/git-upload-pack",
286 				EnumSet.of(DispatcherType.REQUEST));
287 		broken.addServlet(new ServletHolder(gs), "/*");
288 		return broken;
289 	}
290 
291 	private ServletContextHandler addAuthContext(GitServlet gs,
292 			String contextPath, String... methods) {
293 		ServletContextHandler auth = server.addContext('/' + contextPath);
294 		auth.addServlet(new ServletHolder(gs), "/*");
295 		return server.authBasic(auth, methods);
296 	}
297 
298 	private ServletContextHandler addRedirectContext(GitServlet gs) {
299 		ServletContextHandler redirect = server.addContext("/redirect");
300 		redirect.addFilter(new FilterHolder(new Filter() {
301 
302 			// Enables tests for different codes, and for multiple redirects.
303 			// First parameter is the number of redirects, second one is the
304 			// redirect status code that should be used
305 			private Pattern responsePattern = Pattern
306 					.compile("/response/(\\d+)/(30[1237])/");
307 
308 			// Enables tests to specify the context that the request should be
309 			// redirected to in the end. If not present, redirects got to the
310 			// normal /git context.
311 			private Pattern targetPattern = Pattern.compile("/target(/\\w+)/");
312 
313 			@Override
314 			public void init(FilterConfig filterConfig)
315 					throws ServletException {
316 				// empty
317 			}
318 
319 			@Override
320 			public void doFilter(ServletRequest request,
321 					ServletResponse response, FilterChain chain)
322 					throws IOException, ServletException {
323 				final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
324 				final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
325 				final StringBuffer fullUrl = httpServletRequest.getRequestURL();
326 				if (httpServletRequest.getQueryString() != null) {
327 					fullUrl.append("?")
328 							.append(httpServletRequest.getQueryString());
329 				}
330 				String urlString = fullUrl.toString();
331 				if (urlString.contains("/loop/")) {
332 					urlString = urlString.replace("/loop/", "/loop/x/");
333 					if (urlString.contains("/loop/x/x/x/x/x/x/x/x/")) {
334 						// Go back to initial.
335 						urlString = urlString.replace("/loop/x/x/x/x/x/x/x/x/",
336 								"/loop/");
337 					}
338 					httpServletResponse.setStatus(
339 							HttpServletResponse.SC_MOVED_TEMPORARILY);
340 					httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
341 							urlString);
342 					return;
343 				}
344 				int responseCode = HttpServletResponse.SC_MOVED_PERMANENTLY;
345 				int nofRedirects = 0;
346 				Matcher matcher = responsePattern.matcher(urlString);
347 				if (matcher.find()) {
348 					nofRedirects = Integer
349 							.parseUnsignedInt(matcher.group(1));
350 					responseCode = Integer.parseUnsignedInt(matcher.group(2));
351 					if (--nofRedirects <= 0) {
352 						urlString = urlString.substring(0, matcher.start())
353 								+ '/' + urlString.substring(matcher.end());
354 					} else {
355 						urlString = urlString.substring(0, matcher.start())
356 								+ "/response/" + nofRedirects + "/"
357 								+ responseCode + '/'
358 								+ urlString.substring(matcher.end());
359 					}
360 				}
361 				httpServletResponse.setStatus(responseCode);
362 				if (nofRedirects <= 0) {
363 					String targetContext = "/git";
364 					matcher = targetPattern.matcher(urlString);
365 					if (matcher.find()) {
366 						urlString = urlString.substring(0, matcher.start())
367 								+ '/' + urlString.substring(matcher.end());
368 						targetContext = matcher.group(1);
369 					}
370 					urlString = urlString.replace("/redirect", targetContext);
371 				}
372 				httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
373 						urlString);
374 			}
375 
376 			@Override
377 			public void destroy() {
378 				// empty
379 			}
380 		}), "/*", EnumSet.of(DispatcherType.REQUEST));
381 		redirect.addServlet(new ServletHolder(gs), "/*");
382 		return redirect;
383 	}
384 
385 	@Test
386 	public void testListRemote() throws IOException {
387 		Repository dst = createBareRepository();
388 
389 		assertEquals("http", remoteURI.getScheme());
390 
391 		Map<String, Ref> map;
392 		try (Transport t = Transport.open(dst, remoteURI)) {
393 			// I didn't make up these public interface names, I just
394 			// approved them for inclusion into the code base. Sorry.
395 			// --spearce
396 			//
397 			assertTrue("isa TransportHttp", t instanceof TransportHttp);
398 			assertTrue("isa HttpTransport", t instanceof HttpTransport);
399 
400 			try (FetchConnection c = t.openFetch()) {
401 				map = c.getRefsMap();
402 			}
403 		}
404 
405 		assertNotNull("have map of refs", map);
406 		assertEquals(3, map.size());
407 
408 		assertNotNull("has " + master, map.get(master));
409 		assertEquals(B, map.get(master).getObjectId());
410 
411 		assertNotNull("has " + Constants.HEAD, map.get(Constants.HEAD));
412 		assertEquals(B, map.get(Constants.HEAD).getObjectId());
413 
414 		List<AccessEvent> requests = getRequests();
415 		assertEquals(1, requests.size());
416 
417 		AccessEvent info = requests.get(0);
418 		assertEquals("GET", info.getMethod());
419 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
420 		assertEquals(1, info.getParameters().size());
421 		assertEquals("git-upload-pack", info.getParameter("service"));
422 		assertEquals(200, info.getStatus());
423 		assertEquals("application/x-git-upload-pack-advertisement", info
424 				.getResponseHeader(HDR_CONTENT_TYPE));
425 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
426 	}
427 
428 	@Test
429 	public void testListRemote_BadName() throws IOException, URISyntaxException {
430 		Repository dst = createBareRepository();
431 		URIish uri = new URIish(this.remoteURI.toString() + ".invalid");
432 		try (Transport t = Transport.open(dst, uri)) {
433 			try {
434 				t.openFetch();
435 				fail("fetch connection opened");
436 			} catch (RemoteRepositoryException notFound) {
437 				assertEquals(uri + ": Git repository not found",
438 						notFound.getMessage());
439 			}
440 		}
441 
442 		List<AccessEvent> requests = getRequests();
443 		assertEquals(1, requests.size());
444 
445 		AccessEvent info = requests.get(0);
446 		assertEquals("GET", info.getMethod());
447 		assertEquals(join(uri, "info/refs"), info.getPath());
448 		assertEquals(1, info.getParameters().size());
449 		assertEquals("git-upload-pack", info.getParameter("service"));
450 		assertEquals(200, info.getStatus());
451 		assertEquals("application/x-git-upload-pack-advertisement",
452 				info.getResponseHeader(HDR_CONTENT_TYPE));
453 	}
454 
455 	@Test
456 	public void testInitialClone_Small() throws Exception {
457 		Repository dst = createBareRepository();
458 		assertFalse(dst.hasObject(A_txt));
459 
460 		try (Transport t = Transport.open(dst, remoteURI)) {
461 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
462 		}
463 
464 		assertTrue(dst.hasObject(A_txt));
465 		assertEquals(B, dst.exactRef(master).getObjectId());
466 		fsck(dst, B);
467 
468 		List<AccessEvent> requests = getRequests();
469 		assertEquals(2, requests.size());
470 
471 		AccessEvent info = requests.get(0);
472 		assertEquals("GET", info.getMethod());
473 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
474 		assertEquals(1, info.getParameters().size());
475 		assertEquals("git-upload-pack", info.getParameter("service"));
476 		assertEquals(200, info.getStatus());
477 		assertEquals("application/x-git-upload-pack-advertisement", info
478 				.getResponseHeader(HDR_CONTENT_TYPE));
479 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
480 
481 		AccessEvent service = requests.get(1);
482 		assertEquals("POST", service.getMethod());
483 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
484 		assertEquals(0, service.getParameters().size());
485 		assertNotNull("has content-length", service
486 				.getRequestHeader(HDR_CONTENT_LENGTH));
487 		assertNull("not chunked", service
488 				.getRequestHeader(HDR_TRANSFER_ENCODING));
489 
490 		assertEquals(200, service.getStatus());
491 		assertEquals("application/x-git-upload-pack-result", service
492 				.getResponseHeader(HDR_CONTENT_TYPE));
493 	}
494 
495 	private void initialClone_Redirect(int nofRedirects, int code)
496 			throws Exception {
497 		Repository dst = createBareRepository();
498 		assertFalse(dst.hasObject(A_txt));
499 
500 		URIish cloneFrom = redirectURI;
501 		if (code != 301 || nofRedirects > 1) {
502 			cloneFrom = extendPath(cloneFrom,
503 					"/response/" + nofRedirects + "/" + code);
504 		}
505 		try (Transport t = Transport.open(dst, cloneFrom)) {
506 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
507 		}
508 
509 		assertTrue(dst.hasObject(A_txt));
510 		assertEquals(B, dst.exactRef(master).getObjectId());
511 		fsck(dst, B);
512 
513 		List<AccessEvent> requests = getRequests();
514 		assertEquals(2 + nofRedirects, requests.size());
515 
516 		int n = 0;
517 		while (n < nofRedirects) {
518 			AccessEvent redirect = requests.get(n++);
519 			assertEquals(code, redirect.getStatus());
520 		}
521 
522 		AccessEvent info = requests.get(n++);
523 		assertEquals("GET", info.getMethod());
524 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
525 		assertEquals(1, info.getParameters().size());
526 		assertEquals("git-upload-pack", info.getParameter("service"));
527 		assertEquals(200, info.getStatus());
528 		assertEquals("application/x-git-upload-pack-advertisement",
529 				info.getResponseHeader(HDR_CONTENT_TYPE));
530 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
531 
532 		AccessEvent service = requests.get(n++);
533 		assertEquals("POST", service.getMethod());
534 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
535 		assertEquals(0, service.getParameters().size());
536 		assertNotNull("has content-length",
537 				service.getRequestHeader(HDR_CONTENT_LENGTH));
538 		assertNull("not chunked",
539 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
540 
541 		assertEquals(200, service.getStatus());
542 		assertEquals("application/x-git-upload-pack-result",
543 				service.getResponseHeader(HDR_CONTENT_TYPE));
544 	}
545 
546 	@Test
547 	public void testInitialClone_Redirect301Small() throws Exception {
548 		initialClone_Redirect(1, 301);
549 	}
550 
551 	@Test
552 	public void testInitialClone_Redirect302Small() throws Exception {
553 		initialClone_Redirect(1, 302);
554 	}
555 
556 	@Test
557 	public void testInitialClone_Redirect303Small() throws Exception {
558 		initialClone_Redirect(1, 303);
559 	}
560 
561 	@Test
562 	public void testInitialClone_Redirect307Small() throws Exception {
563 		initialClone_Redirect(1, 307);
564 	}
565 
566 	@Test
567 	public void testInitialClone_RedirectMultiple() throws Exception {
568 		initialClone_Redirect(4, 302);
569 	}
570 
571 	@Test
572 	public void testInitialClone_RedirectMax() throws Exception {
573 		FileBasedConfig userConfig = SystemReader.getInstance()
574 				.openUserConfig(null, FS.DETECTED);
575 		userConfig.setInt("http", null, "maxRedirects", 4);
576 		userConfig.save();
577 		initialClone_Redirect(4, 302);
578 	}
579 
580 	@Test
581 	public void testInitialClone_RedirectTooOften() throws Exception {
582 		FileBasedConfig userConfig = SystemReader.getInstance()
583 				.openUserConfig(null, FS.DETECTED);
584 		userConfig.setInt("http", null, "maxRedirects", 3);
585 		userConfig.save();
586 		Repository dst = createBareRepository();
587 		assertFalse(dst.hasObject(A_txt));
588 
589 		URIish cloneFrom = extendPath(redirectURI, "/response/4/302");
590 		String remoteUri = cloneFrom.toString();
591 		try (Transport t = Transport.open(dst, cloneFrom)) {
592 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
593 			fail("Should have failed (too many redirects)");
594 		} catch (TransportException e) {
595 			String expectedMessageBegin = remoteUri.toString() + ": "
596 					+ MessageFormat.format(JGitText.get().redirectLimitExceeded,
597 							"3", remoteUri.replace("/4/", "/1/") + '/', "");
598 			String message = e.getMessage();
599 			if (message.length() > expectedMessageBegin.length()) {
600 				message = message.substring(0, expectedMessageBegin.length());
601 			}
602 			assertEquals(expectedMessageBegin, message);
603 		}
604 	}
605 
606 	@Test
607 	public void testInitialClone_RedirectLoop() throws Exception {
608 		Repository dst = createBareRepository();
609 		assertFalse(dst.hasObject(A_txt));
610 
611 		URIish cloneFrom = extendPath(redirectURI, "/loop");
612 		try (Transport t = Transport.open(dst, cloneFrom)) {
613 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
614 			fail("Should have failed (redirect loop)");
615 		} catch (TransportException e) {
616 			assertTrue(e.getMessage().contains("Redirected more than"));
617 		}
618 	}
619 
620 	@Test
621 	public void testInitialClone_RedirectOnPostAllowed() throws Exception {
622 		FileBasedConfig userConfig = SystemReader.getInstance()
623 				.openUserConfig(null, FS.DETECTED);
624 		userConfig.setString("http", null, "followRedirects", "true");
625 		userConfig.save();
626 		Repository dst = createBareRepository();
627 		assertFalse(dst.hasObject(A_txt));
628 
629 		URIish cloneFrom = extendPath(remoteURI, "/post");
630 		try (Transport t = Transport.open(dst, cloneFrom)) {
631 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
632 		}
633 
634 		assertTrue(dst.hasObject(A_txt));
635 		assertEquals(B, dst.exactRef(master).getObjectId());
636 		fsck(dst, B);
637 
638 		List<AccessEvent> requests = getRequests();
639 		assertEquals(3, requests.size());
640 
641 		AccessEvent info = requests.get(0);
642 		assertEquals("GET", info.getMethod());
643 		assertEquals(join(cloneFrom, "info/refs"), info.getPath());
644 		assertEquals(1, info.getParameters().size());
645 		assertEquals("git-upload-pack", info.getParameter("service"));
646 		assertEquals(200, info.getStatus());
647 		assertEquals("application/x-git-upload-pack-advertisement",
648 				info.getResponseHeader(HDR_CONTENT_TYPE));
649 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
650 
651 		AccessEvent redirect = requests.get(1);
652 		assertEquals("POST", redirect.getMethod());
653 		assertEquals(301, redirect.getStatus());
654 
655 		AccessEvent service = requests.get(2);
656 		assertEquals("POST", service.getMethod());
657 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
658 		assertEquals(0, service.getParameters().size());
659 		assertNotNull("has content-length",
660 				service.getRequestHeader(HDR_CONTENT_LENGTH));
661 		assertNull("not chunked",
662 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
663 
664 		assertEquals(200, service.getStatus());
665 		assertEquals("application/x-git-upload-pack-result",
666 				service.getResponseHeader(HDR_CONTENT_TYPE));
667 	}
668 
669 	@Test
670 	public void testInitialClone_RedirectOnPostForbidden() throws Exception {
671 		Repository dst = createBareRepository();
672 		assertFalse(dst.hasObject(A_txt));
673 
674 		URIish cloneFrom = extendPath(remoteURI, "/post");
675 		try (Transport t = Transport.open(dst, cloneFrom)) {
676 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
677 			fail("Should have failed (redirect on POST)");
678 		} catch (TransportException e) {
679 			assertTrue(e.getMessage().contains("301"));
680 		}
681 	}
682 
683 	@Test
684 	public void testInitialClone_RedirectForbidden() throws Exception {
685 		FileBasedConfig userConfig = SystemReader.getInstance()
686 				.openUserConfig(null, FS.DETECTED);
687 		userConfig.setString("http", null, "followRedirects", "false");
688 		userConfig.save();
689 
690 		Repository dst = createBareRepository();
691 		assertFalse(dst.hasObject(A_txt));
692 
693 		try (Transport t = Transport.open(dst, redirectURI)) {
694 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
695 			fail("Should have failed (redirects forbidden)");
696 		} catch (TransportException e) {
697 			assertTrue(
698 					e.getMessage().contains("http.followRedirects is false"));
699 		}
700 	}
701 
702 	@Test
703 	public void testInitialClone_WithAuthentication() throws Exception {
704 		Repository dst = createBareRepository();
705 		assertFalse(dst.hasObject(A_txt));
706 
707 		try (Transport t = Transport.open(dst, authURI)) {
708 			t.setCredentialsProvider(testCredentials);
709 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
710 		}
711 
712 		assertTrue(dst.hasObject(A_txt));
713 		assertEquals(B, dst.exactRef(master).getObjectId());
714 		fsck(dst, B);
715 
716 		List<AccessEvent> requests = getRequests();
717 		assertEquals(3, requests.size());
718 
719 		AccessEvent info = requests.get(0);
720 		assertEquals("GET", info.getMethod());
721 		assertEquals(401, info.getStatus());
722 
723 		info = requests.get(1);
724 		assertEquals("GET", info.getMethod());
725 		assertEquals(join(authURI, "info/refs"), info.getPath());
726 		assertEquals(1, info.getParameters().size());
727 		assertEquals("git-upload-pack", info.getParameter("service"));
728 		assertEquals(200, info.getStatus());
729 		assertEquals("application/x-git-upload-pack-advertisement",
730 				info.getResponseHeader(HDR_CONTENT_TYPE));
731 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
732 
733 		AccessEvent service = requests.get(2);
734 		assertEquals("POST", service.getMethod());
735 		assertEquals(join(authURI, "git-upload-pack"), service.getPath());
736 		assertEquals(0, service.getParameters().size());
737 		assertNotNull("has content-length",
738 				service.getRequestHeader(HDR_CONTENT_LENGTH));
739 		assertNull("not chunked",
740 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
741 
742 		assertEquals(200, service.getStatus());
743 		assertEquals("application/x-git-upload-pack-result",
744 				service.getResponseHeader(HDR_CONTENT_TYPE));
745 	}
746 
747 	@Test
748 	public void testInitialClone_WithAuthenticationNoCredentials()
749 			throws Exception {
750 		Repository dst = createBareRepository();
751 		assertFalse(dst.hasObject(A_txt));
752 
753 		try (Transport t = Transport.open(dst, authURI)) {
754 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
755 			fail("Should not have succeeded -- no authentication");
756 		} catch (TransportException e) {
757 			String msg = e.getMessage();
758 			assertTrue("Unexpected exception message: " + msg,
759 					msg.contains("no CredentialsProvider"));
760 		}
761 		List<AccessEvent> requests = getRequests();
762 		assertEquals(1, requests.size());
763 
764 		AccessEvent info = requests.get(0);
765 		assertEquals("GET", info.getMethod());
766 		assertEquals(401, info.getStatus());
767 	}
768 
769 	@Test
770 	public void testInitialClone_WithAuthenticationWrongCredentials()
771 			throws Exception {
772 		Repository dst = createBareRepository();
773 		assertFalse(dst.hasObject(A_txt));
774 
775 		try (Transport t = Transport.open(dst, authURI)) {
776 			t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
777 					AppServer.username, "wrongpassword"));
778 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
779 			fail("Should not have succeeded -- wrong password");
780 		} catch (TransportException e) {
781 			String msg = e.getMessage();
782 			assertTrue("Unexpected exception message: " + msg,
783 					msg.contains("auth"));
784 		}
785 		List<AccessEvent> requests = getRequests();
786 		// Once without authentication plus three re-tries with authentication
787 		assertEquals(4, requests.size());
788 
789 		for (AccessEvent event : requests) {
790 			assertEquals("GET", event.getMethod());
791 			assertEquals(401, event.getStatus());
792 		}
793 	}
794 
795 	@Test
796 	public void testInitialClone_WithAuthenticationAfterRedirect()
797 			throws Exception {
798 		Repository dst = createBareRepository();
799 		assertFalse(dst.hasObject(A_txt));
800 
801 		URIish cloneFrom = extendPath(redirectURI, "/target/auth");
802 		CredentialsProvider uriSpecificCredentialsProvider = new UsernamePasswordCredentialsProvider(
803 				"unknown", "none") {
804 			@Override
805 			public boolean get(URIish uri, CredentialItem... items)
806 					throws UnsupportedCredentialItem {
807 				// Only return the true credentials if the uri path starts with
808 				// /auth. This ensures that we do provide the correct
809 				// credentials only for the URi after the redirect, making the
810 				// test fail if we should be asked for the credentials for the
811 				// original URI.
812 				if (uri.getPath().startsWith("/auth")) {
813 					return testCredentials.get(uri, items);
814 				}
815 				return super.get(uri, items);
816 			}
817 		};
818 		try (Transport t = Transport.open(dst, cloneFrom)) {
819 			t.setCredentialsProvider(uriSpecificCredentialsProvider);
820 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
821 		}
822 
823 		assertTrue(dst.hasObject(A_txt));
824 		assertEquals(B, dst.exactRef(master).getObjectId());
825 		fsck(dst, B);
826 
827 		List<AccessEvent> requests = getRequests();
828 		assertEquals(4, requests.size());
829 
830 		AccessEvent redirect = requests.get(0);
831 		assertEquals("GET", redirect.getMethod());
832 		assertEquals(join(cloneFrom, "info/refs"), redirect.getPath());
833 		assertEquals(301, redirect.getStatus());
834 
835 		AccessEvent info = requests.get(1);
836 		assertEquals("GET", info.getMethod());
837 		assertEquals(join(authURI, "info/refs"), info.getPath());
838 		assertEquals(401, info.getStatus());
839 
840 		info = requests.get(2);
841 		assertEquals("GET", info.getMethod());
842 		assertEquals(join(authURI, "info/refs"), info.getPath());
843 		assertEquals(1, info.getParameters().size());
844 		assertEquals("git-upload-pack", info.getParameter("service"));
845 		assertEquals(200, info.getStatus());
846 		assertEquals("application/x-git-upload-pack-advertisement",
847 				info.getResponseHeader(HDR_CONTENT_TYPE));
848 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
849 
850 		AccessEvent service = requests.get(3);
851 		assertEquals("POST", service.getMethod());
852 		assertEquals(join(authURI, "git-upload-pack"), service.getPath());
853 		assertEquals(0, service.getParameters().size());
854 		assertNotNull("has content-length",
855 				service.getRequestHeader(HDR_CONTENT_LENGTH));
856 		assertNull("not chunked",
857 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
858 
859 		assertEquals(200, service.getStatus());
860 		assertEquals("application/x-git-upload-pack-result",
861 				service.getResponseHeader(HDR_CONTENT_TYPE));
862 	}
863 
864 	@Test
865 	public void testInitialClone_WithAuthenticationOnPostOnly()
866 			throws Exception {
867 		Repository dst = createBareRepository();
868 		assertFalse(dst.hasObject(A_txt));
869 
870 		try (Transport t = Transport.open(dst, authOnPostURI)) {
871 			t.setCredentialsProvider(testCredentials);
872 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
873 		}
874 
875 		assertTrue(dst.hasObject(A_txt));
876 		assertEquals(B, dst.exactRef(master).getObjectId());
877 		fsck(dst, B);
878 
879 		List<AccessEvent> requests = getRequests();
880 		assertEquals(3, requests.size());
881 
882 		AccessEvent info = requests.get(0);
883 		assertEquals("GET", info.getMethod());
884 		assertEquals(join(authOnPostURI, "info/refs"), info.getPath());
885 		assertEquals(1, info.getParameters().size());
886 		assertEquals("git-upload-pack", info.getParameter("service"));
887 		assertEquals(200, info.getStatus());
888 		assertEquals("application/x-git-upload-pack-advertisement",
889 				info.getResponseHeader(HDR_CONTENT_TYPE));
890 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
891 
892 		AccessEvent service = requests.get(1);
893 		assertEquals("POST", service.getMethod());
894 		assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath());
895 		assertEquals(401, service.getStatus());
896 
897 		service = requests.get(2);
898 		assertEquals("POST", service.getMethod());
899 		assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath());
900 		assertEquals(0, service.getParameters().size());
901 		assertNotNull("has content-length",
902 				service.getRequestHeader(HDR_CONTENT_LENGTH));
903 		assertNull("not chunked",
904 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
905 
906 		assertEquals(200, service.getStatus());
907 		assertEquals("application/x-git-upload-pack-result",
908 				service.getResponseHeader(HDR_CONTENT_TYPE));
909 	}
910 
911 	@Test
912 	public void testFetch_FewLocalCommits() throws Exception {
913 		// Bootstrap by doing the clone.
914 		//
915 		TestRepository dst = createTestRepository();
916 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
917 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
918 		}
919 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
920 		List<AccessEvent> cloneRequests = getRequests();
921 
922 		// Only create a few new commits.
923 		TestRepository.BranchBuilder b = dst.branch(master);
924 		for (int i = 0; i < 4; i++)
925 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
926 
927 		// Create a new commit on the remote.
928 		//
929 		b = new TestRepository<>(remoteRepository).branch(master);
930 		RevCommit Z = b.commit().message("Z").create();
931 
932 		// Now incrementally update.
933 		//
934 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
935 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
936 		}
937 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
938 
939 		List<AccessEvent> requests = getRequests();
940 		requests.removeAll(cloneRequests);
941 		assertEquals(2, requests.size());
942 
943 		AccessEvent info = requests.get(0);
944 		assertEquals("GET", info.getMethod());
945 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
946 		assertEquals(1, info.getParameters().size());
947 		assertEquals("git-upload-pack", info.getParameter("service"));
948 		assertEquals(200, info.getStatus());
949 		assertEquals("application/x-git-upload-pack-advertisement",
950 				info.getResponseHeader(HDR_CONTENT_TYPE));
951 
952 		// We should have needed one request to perform the fetch.
953 		//
954 		AccessEvent service = requests.get(1);
955 		assertEquals("POST", service.getMethod());
956 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
957 		assertEquals(0, service.getParameters().size());
958 		assertNotNull("has content-length",
959 				service.getRequestHeader(HDR_CONTENT_LENGTH));
960 		assertNull("not chunked",
961 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
962 
963 		assertEquals(200, service.getStatus());
964 		assertEquals("application/x-git-upload-pack-result",
965 				service.getResponseHeader(HDR_CONTENT_TYPE));
966 	}
967 
968 	@Test
969 	public void testFetch_TooManyLocalCommits() throws Exception {
970 		// Bootstrap by doing the clone.
971 		//
972 		TestRepository dst = createTestRepository();
973 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
974 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
975 		}
976 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
977 		List<AccessEvent> cloneRequests = getRequests();
978 
979 		// Force enough into the local client that enumeration will
980 		// need multiple packets, but not too many to overflow and
981 		// not pick up the ACK_COMMON message.
982 		//
983 		TestRepository.BranchBuilder b = dst.branch(master);
984 		for (int i = 0; i < 32 - 1; i++)
985 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
986 
987 		// Create a new commit on the remote.
988 		//
989 		b = new TestRepository<>(remoteRepository).branch(master);
990 		RevCommit Z = b.commit().message("Z").create();
991 
992 		// Now incrementally update.
993 		//
994 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
995 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
996 		}
997 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
998 
999 		List<AccessEvent> requests = getRequests();
1000 		requests.removeAll(cloneRequests);
1001 		assertEquals(3, requests.size());
1002 
1003 		AccessEvent info = requests.get(0);
1004 		assertEquals("GET", info.getMethod());
1005 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1006 		assertEquals(1, info.getParameters().size());
1007 		assertEquals("git-upload-pack", info.getParameter("service"));
1008 		assertEquals(200, info.getStatus());
1009 		assertEquals("application/x-git-upload-pack-advertisement", info
1010 				.getResponseHeader(HDR_CONTENT_TYPE));
1011 
1012 		// We should have needed two requests to perform the fetch
1013 		// due to the high number of local unknown commits.
1014 		//
1015 		AccessEvent service = requests.get(1);
1016 		assertEquals("POST", service.getMethod());
1017 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
1018 		assertEquals(0, service.getParameters().size());
1019 		assertNotNull("has content-length", service
1020 				.getRequestHeader(HDR_CONTENT_LENGTH));
1021 		assertNull("not chunked", service
1022 				.getRequestHeader(HDR_TRANSFER_ENCODING));
1023 
1024 		assertEquals(200, service.getStatus());
1025 		assertEquals("application/x-git-upload-pack-result", service
1026 				.getResponseHeader(HDR_CONTENT_TYPE));
1027 
1028 		service = requests.get(2);
1029 		assertEquals("POST", service.getMethod());
1030 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
1031 		assertEquals(0, service.getParameters().size());
1032 		assertNotNull("has content-length", service
1033 				.getRequestHeader(HDR_CONTENT_LENGTH));
1034 		assertNull("not chunked", service
1035 				.getRequestHeader(HDR_TRANSFER_ENCODING));
1036 
1037 		assertEquals(200, service.getStatus());
1038 		assertEquals("application/x-git-upload-pack-result", service
1039 				.getResponseHeader(HDR_CONTENT_TYPE));
1040 	}
1041 
1042 	@Test
1043 	public void testInitialClone_BrokenServer() throws Exception {
1044 		Repository dst = createBareRepository();
1045 		assertFalse(dst.hasObject(A_txt));
1046 
1047 		try (Transport t = Transport.open(dst, brokenURI)) {
1048 			try {
1049 				t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1050 				fail("fetch completed despite upload-pack being broken");
1051 			} catch (TransportException err) {
1052 				String exp = brokenURI + ": expected"
1053 						+ " Content-Type application/x-git-upload-pack-result;"
1054 						+ " received Content-Type text/plain;charset=utf-8";
1055 				assertEquals(exp, err.getMessage());
1056 			}
1057 		}
1058 
1059 		List<AccessEvent> requests = getRequests();
1060 		assertEquals(2, requests.size());
1061 
1062 		AccessEvent info = requests.get(0);
1063 		assertEquals("GET", info.getMethod());
1064 		assertEquals(join(brokenURI, "info/refs"), info.getPath());
1065 		assertEquals(1, info.getParameters().size());
1066 		assertEquals("git-upload-pack", info.getParameter("service"));
1067 		assertEquals(200, info.getStatus());
1068 		assertEquals("application/x-git-upload-pack-advertisement", info
1069 				.getResponseHeader(HDR_CONTENT_TYPE));
1070 
1071 		AccessEvent service = requests.get(1);
1072 		assertEquals("POST", service.getMethod());
1073 		assertEquals(join(brokenURI, "git-upload-pack"), service.getPath());
1074 		assertEquals(0, service.getParameters().size());
1075 		assertEquals(200, service.getStatus());
1076 		assertEquals("text/plain;charset=utf-8",
1077 				service.getResponseHeader(HDR_CONTENT_TYPE));
1078 	}
1079 
1080 	@Test
1081 	public void testInvalidWant() throws Exception {
1082 		@SuppressWarnings("resource")
1083 		ObjectId id = new ObjectInserter.Formatter().idFor(Constants.OBJ_BLOB,
1084 				"testInvalidWant".getBytes(CHARSET));
1085 
1086 		Repository dst = createBareRepository();
1087 		try (Transport t = Transport.open(dst, remoteURI);
1088 				FetchConnection c = t.openFetch()) {
1089 			Ref want = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(),
1090 					id);
1091 			c.fetch(NullProgressMonitor.INSTANCE, Collections.singleton(want),
1092 					Collections.<ObjectId> emptySet());
1093 			fail("Server accepted want " + id.name());
1094 		} catch (TransportException err) {
1095 			assertEquals("want " + id.name() + " not valid", err.getMessage());
1096 		}
1097 	}
1098 
1099 	@Test
1100 	public void testFetch_RefsUnreadableOnUpload() throws Exception {
1101 		AppServer noRefServer = new AppServer();
1102 		try {
1103 			final String repoName = "refs-unreadable";
1104 			RefsUnreadableInMemoryRepository badRefsRepo = new RefsUnreadableInMemoryRepository(
1105 					new DfsRepositoryDescription(repoName));
1106 			final TestRepository<Repository> repo = new TestRepository<>(
1107 					badRefsRepo);
1108 
1109 			ServletContextHandler app = noRefServer.addContext("/git");
1110 			GitServlet gs = new GitServlet();
1111 			gs.setRepositoryResolver(new TestRepositoryResolver(repo, repoName));
1112 			app.addServlet(new ServletHolder(gs), "/*");
1113 			noRefServer.setUp();
1114 
1115 			RevBlob A2_txt = repo.blob("A2");
1116 			RevCommit A2 = repo.commit().add("A2_txt", A2_txt).create();
1117 			RevCommit B2 = repo.commit().parent(A2).add("A2_txt", "C2")
1118 					.add("B2", "B2").create();
1119 			repo.update(master, B2);
1120 
1121 			URIish badRefsURI = new URIish(noRefServer.getURI()
1122 					.resolve(app.getContextPath() + "/" + repoName).toString());
1123 
1124 			Repository dst = createBareRepository();
1125 			try (Transport t = Transport.open(dst, badRefsURI);
1126 					FetchConnection c = t.openFetch()) {
1127 				// We start failing here to exercise the post-advertisement
1128 				// upload pack handler.
1129 				badRefsRepo.startFailing();
1130 				// Need to flush caches because ref advertisement populated them.
1131 				badRefsRepo.getRefDatabase().refresh();
1132 				c.fetch(NullProgressMonitor.INSTANCE,
1133 						Collections.singleton(c.getRef(master)),
1134 						Collections.<ObjectId> emptySet());
1135 				fail("Successfully served ref with value " + c.getRef(master));
1136 			} catch (TransportException err) {
1137 				assertEquals("internal server error", err.getMessage());
1138 			}
1139 		} finally {
1140 			noRefServer.tearDown();
1141 		}
1142 	}
1143 
1144 	@Test
1145 	public void testPush_NotAuthorized() throws Exception {
1146 		final TestRepository src = createTestRepository();
1147 		final RevBlob Q_txt = src.blob("new text");
1148 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
1149 		final Repository db = src.getRepository();
1150 		final String dstName = Constants.R_HEADS + "new.branch";
1151 
1152 		// push anonymous shouldn't be allowed.
1153 		//
1154 		try (Transport t = Transport.open(db, remoteURI)) {
1155 			final String srcExpr = Q.name();
1156 			final boolean forceUpdate = false;
1157 			final String localName = null;
1158 			final ObjectId oldId = null;
1159 
1160 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
1161 					srcExpr, dstName, forceUpdate, localName, oldId);
1162 			try {
1163 				t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
1164 				fail("anonymous push incorrectly accepted without error");
1165 			} catch (TransportException e) {
1166 				final String exp = remoteURI + ": "
1167 						+ JGitText.get().authenticationNotSupported;
1168 				assertEquals(exp, e.getMessage());
1169 			}
1170 		}
1171 
1172 		List<AccessEvent> requests = getRequests();
1173 		assertEquals(1, requests.size());
1174 
1175 		AccessEvent info = requests.get(0);
1176 		assertEquals("GET", info.getMethod());
1177 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1178 		assertEquals(1, info.getParameters().size());
1179 		assertEquals("git-receive-pack", info.getParameter("service"));
1180 		assertEquals(401, info.getStatus());
1181 	}
1182 
1183 	@Test
1184 	public void testPush_CreateBranch() throws Exception {
1185 		final TestRepository src = createTestRepository();
1186 		final RevBlob Q_txt = src.blob("new text");
1187 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
1188 		final Repository db = src.getRepository();
1189 		final String dstName = Constants.R_HEADS + "new.branch";
1190 
1191 		enableReceivePack();
1192 
1193 		try (Transport t = Transport.open(db, remoteURI)) {
1194 			final String srcExpr = Q.name();
1195 			final boolean forceUpdate = false;
1196 			final String localName = null;
1197 			final ObjectId oldId = null;
1198 
1199 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
1200 					srcExpr, dstName, forceUpdate, localName, oldId);
1201 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
1202 		}
1203 
1204 		assertTrue(remoteRepository.hasObject(Q_txt));
1205 		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
1206 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
1207 		fsck(remoteRepository, Q);
1208 
1209 		final ReflogReader log = remoteRepository.getReflogReader(dstName);
1210 		assertNotNull("has log for " + dstName, log);
1211 
1212 		final ReflogEntry last = log.getLastEntry();
1213 		assertNotNull("has last entry", last);
1214 		assertEquals(ObjectId.zeroId(), last.getOldId());
1215 		assertEquals(Q, last.getNewId());
1216 		assertEquals("anonymous", last.getWho().getName());
1217 
1218 		// Assumption: The host name we use to contact the server should
1219 		// be the server's own host name, because it should be the loopback
1220 		// network interface.
1221 		//
1222 		final String clientHost = remoteURI.getHost();
1223 		assertEquals("anonymous@" + clientHost, last.getWho().getEmailAddress());
1224 		assertEquals("push: created", last.getComment());
1225 
1226 		List<AccessEvent> requests = getRequests();
1227 		assertEquals(2, requests.size());
1228 
1229 		AccessEvent info = requests.get(0);
1230 		assertEquals("GET", info.getMethod());
1231 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1232 		assertEquals(1, info.getParameters().size());
1233 		assertEquals("git-receive-pack", info.getParameter("service"));
1234 		assertEquals(200, info.getStatus());
1235 		assertEquals("application/x-git-receive-pack-advertisement", info
1236 				.getResponseHeader(HDR_CONTENT_TYPE));
1237 
1238 		AccessEvent service = requests.get(1);
1239 		assertEquals("POST", service.getMethod());
1240 		assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
1241 		assertEquals(0, service.getParameters().size());
1242 		assertNotNull("has content-length", service
1243 				.getRequestHeader(HDR_CONTENT_LENGTH));
1244 		assertNull("not chunked", service
1245 				.getRequestHeader(HDR_TRANSFER_ENCODING));
1246 
1247 		assertEquals(200, service.getStatus());
1248 		assertEquals("application/x-git-receive-pack-result", service
1249 				.getResponseHeader(HDR_CONTENT_TYPE));
1250 	}
1251 
1252 	@Test
1253 	public void testPush_ChunkedEncoding() throws Exception {
1254 		final TestRepository<Repository> src = createTestRepository();
1255 		final RevBlob Q_bin = src.blob(new TestRng("Q").nextBytes(128 * 1024));
1256 		final RevCommit Q = src.commit().add("Q", Q_bin).create();
1257 		final Repository db = src.getRepository();
1258 		final String dstName = Constants.R_HEADS + "new.branch";
1259 
1260 		enableReceivePack();
1261 
1262 		final StoredConfig cfg = db.getConfig();
1263 		cfg.setInt("core", null, "compression", 0);
1264 		cfg.setInt("http", null, "postbuffer", 8 * 1024);
1265 		cfg.save();
1266 
1267 		try (Transport t = Transport.open(db, remoteURI)) {
1268 			final String srcExpr = Q.name();
1269 			final boolean forceUpdate = false;
1270 			final String localName = null;
1271 			final ObjectId oldId = null;
1272 
1273 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
1274 					srcExpr, dstName, forceUpdate, localName, oldId);
1275 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
1276 		}
1277 
1278 		assertTrue(remoteRepository.hasObject(Q_bin));
1279 		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
1280 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
1281 		fsck(remoteRepository, Q);
1282 
1283 		List<AccessEvent> requests = getRequests();
1284 		assertEquals(2, requests.size());
1285 
1286 		AccessEvent info = requests.get(0);
1287 		assertEquals("GET", info.getMethod());
1288 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1289 		assertEquals(1, info.getParameters().size());
1290 		assertEquals("git-receive-pack", info.getParameter("service"));
1291 		assertEquals(200, info.getStatus());
1292 		assertEquals("application/x-git-receive-pack-advertisement", info
1293 				.getResponseHeader(HDR_CONTENT_TYPE));
1294 
1295 		AccessEvent service = requests.get(1);
1296 		assertEquals("POST", service.getMethod());
1297 		assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
1298 		assertEquals(0, service.getParameters().size());
1299 		assertNull("no content-length", service
1300 				.getRequestHeader(HDR_CONTENT_LENGTH));
1301 		assertEquals("chunked", service.getRequestHeader(HDR_TRANSFER_ENCODING));
1302 
1303 		assertEquals(200, service.getStatus());
1304 		assertEquals("application/x-git-receive-pack-result", service
1305 				.getResponseHeader(HDR_CONTENT_TYPE));
1306 	}
1307 
1308 	private void enableReceivePack() throws IOException {
1309 		final StoredConfig cfg = remoteRepository.getConfig();
1310 		cfg.setBoolean("http", null, "receivepack", true);
1311 		cfg.save();
1312 	}
1313 
1314 }