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