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