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