View Javadoc
1   /*
2    * Copyright (C) 2010, 2020 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.ByteArrayOutputStream;
26  import java.io.File;
27  import java.io.IOException;
28  import java.io.OutputStreamWriter;
29  import java.io.PrintWriter;
30  import java.io.Writer;
31  import java.net.Proxy;
32  import java.net.URI;
33  import java.net.URISyntaxException;
34  import java.net.URL;
35  import java.nio.charset.StandardCharsets;
36  import java.text.MessageFormat;
37  import java.util.Collections;
38  import java.util.EnumSet;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.regex.Matcher;
42  import java.util.regex.Pattern;
43  
44  import javax.servlet.DispatcherType;
45  import javax.servlet.Filter;
46  import javax.servlet.FilterChain;
47  import javax.servlet.FilterConfig;
48  import javax.servlet.RequestDispatcher;
49  import javax.servlet.ServletException;
50  import javax.servlet.ServletRequest;
51  import javax.servlet.ServletResponse;
52  import javax.servlet.http.HttpServletRequest;
53  import javax.servlet.http.HttpServletResponse;
54  
55  import org.eclipse.jetty.servlet.FilterHolder;
56  import org.eclipse.jetty.servlet.ServletContextHandler;
57  import org.eclipse.jetty.servlet.ServletHolder;
58  import org.eclipse.jgit.api.Git;
59  import org.eclipse.jgit.api.TransportConfigCallback;
60  import org.eclipse.jgit.errors.NoRemoteRepositoryException;
61  import org.eclipse.jgit.errors.TransportException;
62  import org.eclipse.jgit.errors.UnsupportedCredentialItem;
63  import org.eclipse.jgit.http.server.GitServlet;
64  import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory;
65  import org.eclipse.jgit.internal.JGitText;
66  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
67  import org.eclipse.jgit.junit.TestRepository;
68  import org.eclipse.jgit.junit.TestRng;
69  import org.eclipse.jgit.junit.http.AccessEvent;
70  import org.eclipse.jgit.junit.http.AppServer;
71  import org.eclipse.jgit.lib.ConfigConstants;
72  import org.eclipse.jgit.lib.Constants;
73  import org.eclipse.jgit.lib.NullProgressMonitor;
74  import org.eclipse.jgit.lib.ObjectId;
75  import org.eclipse.jgit.lib.ObjectIdRef;
76  import org.eclipse.jgit.lib.ObjectInserter;
77  import org.eclipse.jgit.lib.Ref;
78  import org.eclipse.jgit.lib.ReflogEntry;
79  import org.eclipse.jgit.lib.ReflogReader;
80  import org.eclipse.jgit.lib.Repository;
81  import org.eclipse.jgit.lib.StoredConfig;
82  import org.eclipse.jgit.lib.TextProgressMonitor;
83  import org.eclipse.jgit.revwalk.RevBlob;
84  import org.eclipse.jgit.revwalk.RevCommit;
85  import org.eclipse.jgit.revwalk.RevWalk;
86  import org.eclipse.jgit.transport.AbstractAdvertiseRefsHook;
87  import org.eclipse.jgit.transport.AdvertiseRefsHook;
88  import org.eclipse.jgit.transport.CredentialItem;
89  import org.eclipse.jgit.transport.CredentialsProvider;
90  import org.eclipse.jgit.transport.FetchConnection;
91  import org.eclipse.jgit.transport.HttpTransport;
92  import org.eclipse.jgit.transport.RefSpec;
93  import org.eclipse.jgit.transport.RemoteRefUpdate;
94  import org.eclipse.jgit.transport.Transport;
95  import org.eclipse.jgit.transport.TransportHttp;
96  import org.eclipse.jgit.transport.URIish;
97  import org.eclipse.jgit.transport.UploadPack;
98  import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
99  import org.eclipse.jgit.transport.http.HttpConnection;
100 import org.eclipse.jgit.transport.http.HttpConnectionFactory;
101 import org.eclipse.jgit.util.HttpSupport;
102 import org.eclipse.jgit.util.SystemReader;
103 import org.junit.Before;
104 import org.junit.Test;
105 
106 public class SmartClientSmartServerTest extends AllProtocolsHttpTestCase {
107 	private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding";
108 
109 	private AdvertiseRefsHook advertiseRefsHook;
110 
111 	private Repository remoteRepository;
112 
113 	private CredentialsProvider testCredentials = new UsernamePasswordCredentialsProvider(
114 			AppServer.username, AppServer.password);
115 
116 	private URIish remoteURI;
117 
118 	private URIish brokenURI;
119 
120 	private URIish redirectURI;
121 
122 	private URIish authURI;
123 
124 	private URIish authOnPostURI;
125 
126 	private URIish slowURI;
127 
128 	private URIish slowAuthURI;
129 
130 	private RevBlob A_txt;
131 
132 	private RevCommit A, B, unreachableCommit;
133 
134 	public SmartClientSmartServerTest(TestParameters params) {
135 		super(params);
136 	}
137 
138 	@Override
139 	@Before
140 	public void setUp() throws Exception {
141 		super.setUp();
142 
143 		final TestRepository<Repository> src = createTestRepository();
144 		final String srcName = src.getRepository().getDirectory().getName();
145 		StoredConfig cfg = src.getRepository().getConfig();
146 		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
147 				ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
148 		cfg.setInt("protocol", null, "version", enableProtocolV2 ? 2 : 0);
149 		cfg.save();
150 
151 		GitServlet gs = new GitServlet();
152 		gs.setUploadPackFactory((HttpServletRequest req, Repository db) -> {
153 			DefaultUploadPackFactory f = new DefaultUploadPackFactory();
154 			UploadPack up = f.create(req, db);
155 			if (advertiseRefsHook != null) {
156 				up.setAdvertiseRefsHook(advertiseRefsHook);
157 			}
158 			return up;
159 		});
160 
161 		ServletContextHandler app = addNormalContext(gs, src, srcName);
162 
163 		ServletContextHandler broken = addBrokenContext(gs, srcName);
164 
165 		ServletContextHandler redirect = addRedirectContext(gs);
166 
167 		ServletContextHandler auth = addAuthContext(gs, "auth");
168 
169 		ServletContextHandler authOnPost = addAuthContext(gs, "pauth", "POST");
170 
171 		ServletContextHandler slow = addSlowContext(gs, "slow", false);
172 
173 		ServletContextHandler slowAuth = addSlowContext(gs, "slowAuth", true);
174 
175 		server.setUp();
176 
177 		remoteRepository = src.getRepository();
178 		remoteURI = toURIish(app, srcName);
179 		brokenURI = toURIish(broken, srcName);
180 		redirectURI = toURIish(redirect, srcName);
181 		authURI = toURIish(auth, srcName);
182 		authOnPostURI = toURIish(authOnPost, srcName);
183 		slowURI = toURIish(slow, srcName);
184 		slowAuthURI = toURIish(slowAuth, srcName);
185 
186 		A_txt = src.blob("A");
187 		A = src.commit().add("A_txt", A_txt).create();
188 		B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create();
189 		src.update(master, B);
190 
191 		unreachableCommit = src.commit().add("A_txt", A_txt).create();
192 
193 		src.update("refs/garbage/a/very/long/ref/name/to/compress", B);
194 	}
195 
196 	private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
197 		ServletContextHandler app = server.addContext("/git");
198 		app.addFilter(new FilterHolder(new Filter() {
199 
200 			@Override
201 			public void init(FilterConfig filterConfig)
202 					throws ServletException {
203 				// empty
204 			}
205 
206 			// Does an internal forward for GET requests containing "/post/",
207 			// and issues a 301 redirect on POST requests for such URLs. Used
208 			// in the POST redirect tests.
209 			@Override
210 			public void doFilter(ServletRequest request,
211 					ServletResponse response, FilterChain chain)
212 					throws IOException, ServletException {
213 				final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
214 				final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
215 				final StringBuffer fullUrl = httpServletRequest.getRequestURL();
216 				if (httpServletRequest.getQueryString() != null) {
217 					fullUrl.append("?")
218 							.append(httpServletRequest.getQueryString());
219 				}
220 				String urlString = fullUrl.toString();
221 				if ("POST".equalsIgnoreCase(httpServletRequest.getMethod())) {
222 					httpServletResponse.setStatus(
223 							HttpServletResponse.SC_MOVED_PERMANENTLY);
224 					httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
225 							urlString.replace("/post/", "/"));
226 				} else {
227 					String path = httpServletRequest.getPathInfo();
228 					path = path.replace("/post/", "/");
229 					if (httpServletRequest.getQueryString() != null) {
230 						path += '?' + httpServletRequest.getQueryString();
231 					}
232 					RequestDispatcher dispatcher = httpServletRequest
233 							.getRequestDispatcher(path);
234 					dispatcher.forward(httpServletRequest, httpServletResponse);
235 				}
236 			}
237 
238 			@Override
239 			public void destroy() {
240 				// empty
241 			}
242 		}), "/post/*", EnumSet.of(DispatcherType.REQUEST));
243 		gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName));
244 		app.addServlet(new ServletHolder(gs), "/*");
245 		return app;
246 	}
247 
248 	private ServletContextHandler addBrokenContext(GitServlet gs,
249 			String srcName) {
250 		ServletContextHandler broken = server.addContext("/bad");
251 		broken.addFilter(new FilterHolder(new Filter() {
252 
253 			@Override
254 			public void doFilter(ServletRequest request,
255 					ServletResponse response, FilterChain chain)
256 					throws IOException, ServletException {
257 				final HttpServletResponse r = (HttpServletResponse) response;
258 				r.setContentType("text/plain");
259 				r.setCharacterEncoding(UTF_8.name());
260 				try (PrintWriter w = r.getWriter()) {
261 					w.print("OK");
262 				}
263 			}
264 
265 			@Override
266 			public void init(FilterConfig filterConfig)
267 					throws ServletException {
268 				// empty
269 			}
270 
271 			@Override
272 			public void destroy() {
273 				// empty
274 			}
275 		}), "/" + srcName + "/git-upload-pack",
276 				EnumSet.of(DispatcherType.REQUEST));
277 		broken.addServlet(new ServletHolder(gs), "/*");
278 		return broken;
279 	}
280 
281 	private ServletContextHandler addAuthContext(GitServlet gs,
282 			String contextPath, String... methods) {
283 		ServletContextHandler auth = server.addContext('/' + contextPath);
284 		auth.addServlet(new ServletHolder(gs), "/*");
285 		return server.authBasic(auth, methods);
286 	}
287 
288 	private ServletContextHandler addRedirectContext(GitServlet gs) {
289 		ServletContextHandler redirect = server.addContext("/redirect");
290 		redirect.addFilter(new FilterHolder(new Filter() {
291 
292 			// Enables tests for different codes, and for multiple redirects.
293 			// First parameter is the number of redirects, second one is the
294 			// redirect status code that should be used
295 			private Pattern responsePattern = Pattern
296 					.compile("/response/(\\d+)/(30[1237])/");
297 
298 			// Enables tests to specify the context that the request should be
299 			// redirected to in the end. If not present, redirects got to the
300 			// normal /git context.
301 			private Pattern targetPattern = Pattern.compile("/target(/\\w+)/");
302 
303 			@Override
304 			public void init(FilterConfig filterConfig)
305 					throws ServletException {
306 				// empty
307 			}
308 
309 			private String local(String url, boolean toLocal) {
310 				if (!toLocal) {
311 					return url;
312 				}
313 				try {
314 					URI u = new URI(url);
315 					String fragment = u.getRawFragment();
316 					if (fragment != null) {
317 						return u.getRawPath() + '#' + fragment;
318 					}
319 					return u.getRawPath();
320 				} catch (URISyntaxException e) {
321 					return url;
322 				}
323 			}
324 
325 			@Override
326 			public void doFilter(ServletRequest request,
327 					ServletResponse response, FilterChain chain)
328 					throws IOException, ServletException {
329 				final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
330 				final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
331 				final StringBuffer fullUrl = httpServletRequest.getRequestURL();
332 				if (httpServletRequest.getQueryString() != null) {
333 					fullUrl.append("?")
334 							.append(httpServletRequest.getQueryString());
335 				}
336 				String urlString = fullUrl.toString();
337 				boolean localRedirect = false;
338 				if (urlString.contains("/local")) {
339 					urlString = urlString.replace("/local", "");
340 					localRedirect = true;
341 				}
342 				if (urlString.contains("/loop/")) {
343 					urlString = urlString.replace("/loop/", "/loop/x/");
344 					if (urlString.contains("/loop/x/x/x/x/x/x/x/x/")) {
345 						// Go back to initial.
346 						urlString = urlString.replace("/loop/x/x/x/x/x/x/x/x/",
347 								"/loop/");
348 					}
349 					httpServletResponse.setStatus(
350 							HttpServletResponse.SC_MOVED_TEMPORARILY);
351 					httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
352 							local(urlString, localRedirect));
353 					return;
354 				}
355 				int responseCode = HttpServletResponse.SC_MOVED_PERMANENTLY;
356 				int nofRedirects = 0;
357 				Matcher matcher = responsePattern.matcher(urlString);
358 				if (matcher.find()) {
359 					nofRedirects = Integer
360 							.parseUnsignedInt(matcher.group(1));
361 					responseCode = Integer.parseUnsignedInt(matcher.group(2));
362 					if (--nofRedirects <= 0) {
363 						urlString = urlString.substring(0, matcher.start())
364 								+ '/' + urlString.substring(matcher.end());
365 					} else {
366 						urlString = urlString.substring(0, matcher.start())
367 								+ "/response/" + nofRedirects + "/"
368 								+ responseCode + '/'
369 								+ urlString.substring(matcher.end());
370 					}
371 				}
372 				httpServletResponse.setStatus(responseCode);
373 				if (nofRedirects <= 0) {
374 					String targetContext = "/git";
375 					matcher = targetPattern.matcher(urlString);
376 					if (matcher.find()) {
377 						urlString = urlString.substring(0, matcher.start())
378 								+ '/' + urlString.substring(matcher.end());
379 						targetContext = matcher.group(1);
380 					}
381 					urlString = urlString.replace("/redirect", targetContext);
382 
383 				}
384 				httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
385 						local(urlString, localRedirect));
386 			}
387 
388 			@Override
389 			public void destroy() {
390 				// empty
391 			}
392 		}), "/*", EnumSet.of(DispatcherType.REQUEST));
393 		redirect.addServlet(new ServletHolder(gs), "/*");
394 		return redirect;
395 	}
396 
397 	private ServletContextHandler addSlowContext(GitServlet gs, String path,
398 			boolean auth) {
399 		ServletContextHandler slow = server.addContext('/' + path);
400 		slow.addFilter(new FilterHolder(new Filter() {
401 
402 			@Override
403 			public void init(FilterConfig filterConfig)
404 					throws ServletException {
405 				// empty
406 			}
407 
408 			// Simply delays the servlet for two seconds. Used for timeout
409 			// tests, which use a one-second timeout.
410 			@Override
411 			public void doFilter(ServletRequest request,
412 					ServletResponse response, FilterChain chain)
413 					throws IOException, ServletException {
414 				try {
415 					Thread.sleep(2000);
416 				} catch (InterruptedException e) {
417 					throw new IOException(e);
418 				}
419 				chain.doFilter(request, response);
420 			}
421 
422 			@Override
423 			public void destroy() {
424 				// empty
425 			}
426 		}), "/*", EnumSet.of(DispatcherType.REQUEST));
427 		slow.addServlet(new ServletHolder(gs), "/*");
428 		if (auth) {
429 			return server.authBasic(slow);
430 		}
431 		return slow;
432 	}
433 
434 	@Test
435 	public void testListRemote() throws IOException {
436 		assertEquals("http", remoteURI.getScheme());
437 
438 		Map<String, Ref> map;
439 		try (Repository dst = createBareRepository();
440 				Transport t = Transport.open(dst, remoteURI)) {
441 			// I didn't make up these public interface names, I just
442 			// approved them for inclusion into the code base. Sorry.
443 			// --spearce
444 			//
445 			assertTrue("isa TransportHttp", t instanceof TransportHttp);
446 			assertTrue("isa HttpTransport", t instanceof HttpTransport);
447 
448 			try (FetchConnection c = t.openFetch()) {
449 				map = c.getRefsMap();
450 			}
451 		}
452 
453 		assertNotNull("have map of refs", map);
454 		assertEquals(3, map.size());
455 
456 		assertNotNull("has " + master, map.get(master));
457 		assertEquals(B, map.get(master).getObjectId());
458 
459 		assertNotNull("has " + Constants.HEAD, map.get(Constants.HEAD));
460 		assertEquals(B, map.get(Constants.HEAD).getObjectId());
461 
462 		List<AccessEvent> requests = getRequests();
463 		assertEquals(enableProtocolV2 ? 2 : 1, requests.size());
464 
465 		AccessEvent info = requests.get(0);
466 		assertEquals("GET", info.getMethod());
467 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
468 		assertEquals(1, info.getParameters().size());
469 		assertEquals("git-upload-pack", info.getParameter("service"));
470 		assertEquals(200, info.getStatus());
471 		assertEquals("application/x-git-upload-pack-advertisement", info
472 				.getResponseHeader(HDR_CONTENT_TYPE));
473 		if (!enableProtocolV2) {
474 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
475 		} else {
476 			AccessEvent lsRefs = requests.get(1);
477 			assertEquals("POST", lsRefs.getMethod());
478 			assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
479 			assertEquals(0, lsRefs.getParameters().size());
480 			assertNotNull("has content-length",
481 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
482 			assertNull("not chunked",
483 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
484 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
485 			assertEquals(200, lsRefs.getStatus());
486 			assertEquals("application/x-git-upload-pack-result",
487 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
488 		}
489 	}
490 
491 	@Test
492 	public void testListRemote_BadName() throws IOException, URISyntaxException {
493 		URIish uri = new URIish(this.remoteURI.toString() + ".invalid");
494 		try (Repository dst = createBareRepository();
495 				Transport t = Transport.open(dst, uri)) {
496 			try {
497 				t.openFetch();
498 				fail("fetch connection opened");
499 			} catch (NoRemoteRepositoryException notFound) {
500 				assertEquals(uri + ": " + uri
501 						+ "/info/refs?service=git-upload-pack not found: Not Found",
502 						notFound.getMessage());
503 			}
504 		}
505 
506 		List<AccessEvent> requests = getRequests();
507 		assertEquals(1, requests.size());
508 
509 		AccessEvent info = requests.get(0);
510 		assertEquals("GET", info.getMethod());
511 		assertEquals(join(uri, "info/refs"), info.getPath());
512 		assertEquals(1, info.getParameters().size());
513 		assertEquals("git-upload-pack", info.getParameter("service"));
514 		assertEquals(404, info.getStatus());
515 		assertEquals("application/x-git-upload-pack-advertisement",
516 				info.getResponseHeader(HDR_CONTENT_TYPE));
517 	}
518 
519 	@Test
520 	public void testFetchBySHA1() throws Exception {
521 		try (Repository dst = createBareRepository();
522 				Transport t = Transport.open(dst, remoteURI)) {
523 			assertFalse(dst.getObjectDatabase().has(A_txt));
524 			t.fetch(NullProgressMonitor.INSTANCE,
525 					Collections.singletonList(new RefSpec(B.name())));
526 			assertTrue(dst.getObjectDatabase().has(A_txt));
527 		}
528 	}
529 
530 	@Test
531 	public void testFetchBySHA1Unreachable() throws Exception {
532 		try (Repository dst = createBareRepository();
533 				Transport t = Transport.open(dst, remoteURI)) {
534 			assertFalse(dst.getObjectDatabase().has(A_txt));
535 			Exception e = assertThrows(TransportException.class,
536 					() -> t.fetch(NullProgressMonitor.INSTANCE,
537 							Collections.singletonList(
538 									new RefSpec(unreachableCommit.name()))));
539 			assertTrue(e.getMessage().contains(
540 					"Bad Request"));
541 		}
542 		assertLastRequestStatusCode(400);
543 	}
544 
545 	@Test
546 	public void testFetchBySHA1UnreachableByAdvertiseRefsHook()
547 			throws Exception {
548 		advertiseRefsHook = new AbstractAdvertiseRefsHook() {
549 			@Override
550 			protected Map<String, Ref> getAdvertisedRefs(Repository repository,
551 					RevWalk revWalk) {
552 				return Collections.emptyMap();
553 			}
554 		};
555 
556 		try (Repository dst = createBareRepository();
557 				Transport t = Transport.open(dst, remoteURI)) {
558 			assertFalse(dst.getObjectDatabase().has(A_txt));
559 			Exception e = assertThrows(TransportException.class,
560 					() -> t.fetch(NullProgressMonitor.INSTANCE,
561 							Collections.singletonList(new RefSpec(A.name()))));
562 			assertTrue(
563 					e.getMessage().contains("Bad Request"));
564 		}
565 		assertLastRequestStatusCode(400);
566 	}
567 
568 	@Test
569 	public void testTimeoutExpired() throws Exception {
570 		try (Repository dst = createBareRepository();
571 				Transport t = Transport.open(dst, slowURI)) {
572 			t.setTimeout(1);
573 			TransportException expected = assertThrows(TransportException.class,
574 					() -> t.fetch(NullProgressMonitor.INSTANCE,
575 							mirror(master)));
576 			assertTrue("Unexpected exception message: " + expected.toString(),
577 					expected.getMessage().contains("time"));
578 		}
579 	}
580 
581 	@Test
582 	public void testTimeoutExpiredWithAuth() throws Exception {
583 		try (Repository dst = createBareRepository();
584 				Transport t = Transport.open(dst, slowAuthURI)) {
585 			t.setTimeout(1);
586 			t.setCredentialsProvider(testCredentials);
587 			TransportException expected = assertThrows(TransportException.class,
588 					() -> t.fetch(NullProgressMonitor.INSTANCE,
589 							mirror(master)));
590 			assertTrue("Unexpected exception message: " + expected.toString(),
591 					expected.getMessage().contains("time"));
592 			assertFalse("Unexpected exception message: " + expected.toString(),
593 					expected.getMessage().contains("auth"));
594 		}
595 	}
596 
597 	@Test
598 	public void testInitialClone_Small() throws Exception {
599 		try (Repository dst = createBareRepository();
600 				Transport t = Transport.open(dst, remoteURI)) {
601 			assertFalse(dst.getObjectDatabase().has(A_txt));
602 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
603 			assertTrue(dst.getObjectDatabase().has(A_txt));
604 			assertEquals(B, dst.exactRef(master).getObjectId());
605 			fsck(dst, B);
606 		}
607 
608 		List<AccessEvent> requests = getRequests();
609 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
610 
611 		int requestNumber = 0;
612 		AccessEvent info = requests.get(requestNumber++);
613 		assertEquals("GET", info.getMethod());
614 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
615 		assertEquals(1, info.getParameters().size());
616 		assertEquals("git-upload-pack", info.getParameter("service"));
617 		assertEquals(200, info.getStatus());
618 		assertEquals("application/x-git-upload-pack-advertisement", info
619 				.getResponseHeader(HDR_CONTENT_TYPE));
620 		if (!enableProtocolV2) {
621 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
622 		} else {
623 			AccessEvent lsRefs = requests.get(requestNumber++);
624 			assertEquals("POST", lsRefs.getMethod());
625 			assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
626 			assertEquals(0, lsRefs.getParameters().size());
627 			assertNotNull("has content-length",
628 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
629 			assertNull("not chunked",
630 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
631 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
632 			assertEquals(200, lsRefs.getStatus());
633 			assertEquals("application/x-git-upload-pack-result",
634 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
635 		}
636 
637 		AccessEvent service = requests.get(requestNumber);
638 		assertEquals("POST", service.getMethod());
639 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
640 		assertEquals(0, service.getParameters().size());
641 		assertNotNull("has content-length", service
642 				.getRequestHeader(HDR_CONTENT_LENGTH));
643 		assertNull("not chunked", service
644 				.getRequestHeader(HDR_TRANSFER_ENCODING));
645 
646 		assertEquals(200, service.getStatus());
647 		assertEquals("application/x-git-upload-pack-result", service
648 				.getResponseHeader(HDR_CONTENT_TYPE));
649 	}
650 
651 	@Test
652 	public void test_CloneWithCustomFactory() throws Exception {
653 		HttpConnectionFactory globalFactory = HttpTransport
654 				.getConnectionFactory();
655 		HttpConnectionFactory failingConnectionFactory = new HttpConnectionFactory() {
656 
657 			@Override
658 			public HttpConnection create(URL url) throws IOException {
659 				throw new IOException("Should not be reached");
660 			}
661 
662 			@Override
663 			public HttpConnection create(URL url, Proxy proxy)
664 					throws IOException {
665 				throw new IOException("Should not be reached");
666 			}
667 		};
668 		HttpTransport.setConnectionFactory(failingConnectionFactory);
669 		try {
670 			File tmp = createTempDirectory("cloneViaApi");
671 			boolean[] localFactoryUsed = { false };
672 			TransportConfigCallback callback = new TransportConfigCallback() {
673 
674 				@Override
675 				public void configure(Transport transport) {
676 					if (transport instanceof TransportHttp) {
677 						((TransportHttp) transport).setHttpConnectionFactory(
678 								new HttpConnectionFactory() {
679 
680 									@Override
681 									public HttpConnection create(URL url)
682 											throws IOException {
683 										localFactoryUsed[0] = true;
684 										return globalFactory.create(url);
685 									}
686 
687 									@Override
688 									public HttpConnection create(URL url,
689 											Proxy proxy) throws IOException {
690 										localFactoryUsed[0] = true;
691 										return globalFactory.create(url, proxy);
692 									}
693 								});
694 					}
695 				}
696 			};
697 			try (Git git = Git.cloneRepository().setDirectory(tmp)
698 					.setTransportConfigCallback(callback)
699 					.setURI(remoteURI.toPrivateString()).call()) {
700 				assertTrue("Should have used the local HttpConnectionFactory",
701 						localFactoryUsed[0]);
702 			}
703 		} finally {
704 			HttpTransport.setConnectionFactory(globalFactory);
705 		}
706 	}
707 
708 	private void initialClone_Redirect(int nofRedirects, int code)
709 			throws Exception {
710 		initialClone_Redirect(nofRedirects, code, false);
711 	}
712 
713 	private void initialClone_Redirect(int nofRedirects, int code,
714 			boolean localRedirect) throws Exception {
715 		URIish cloneFrom = redirectURI;
716 		if (localRedirect) {
717 			cloneFrom = extendPath(cloneFrom, "/local");
718 		}
719 		if (code != 301 || nofRedirects > 1) {
720 			cloneFrom = extendPath(cloneFrom,
721 					"/response/" + nofRedirects + "/" + code);
722 		}
723 
724 		try (Repository dst = createBareRepository();
725 				Transport t = Transport.open(dst, cloneFrom)) {
726 			assertFalse(dst.getObjectDatabase().has(A_txt));
727 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
728 			assertTrue(dst.getObjectDatabase().has(A_txt));
729 			assertEquals(B, dst.exactRef(master).getObjectId());
730 			fsck(dst, B);
731 		}
732 
733 		List<AccessEvent> requests = getRequests();
734 		assertEquals((enableProtocolV2 ? 3 : 2) + nofRedirects,
735 				requests.size());
736 
737 		int n = 0;
738 		while (n < nofRedirects) {
739 			AccessEvent redirect = requests.get(n++);
740 			assertEquals(code, redirect.getStatus());
741 		}
742 
743 		AccessEvent info = requests.get(n++);
744 		assertEquals("GET", info.getMethod());
745 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
746 		assertEquals(1, info.getParameters().size());
747 		assertEquals("git-upload-pack", info.getParameter("service"));
748 		assertEquals(200, info.getStatus());
749 		assertEquals("application/x-git-upload-pack-advertisement",
750 				info.getResponseHeader(HDR_CONTENT_TYPE));
751 		if (!enableProtocolV2) {
752 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
753 		} else {
754 			AccessEvent lsRefs = requests.get(n++);
755 			assertEquals("POST", lsRefs.getMethod());
756 			assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
757 			assertEquals(0, lsRefs.getParameters().size());
758 			assertNotNull("has content-length",
759 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
760 			assertNull("not chunked",
761 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
762 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
763 			assertEquals(200, lsRefs.getStatus());
764 			assertEquals("application/x-git-upload-pack-result",
765 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
766 		}
767 
768 		AccessEvent service = requests.get(n++);
769 		assertEquals("POST", service.getMethod());
770 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
771 		assertEquals(0, service.getParameters().size());
772 		assertNotNull("has content-length",
773 				service.getRequestHeader(HDR_CONTENT_LENGTH));
774 		assertNull("not chunked",
775 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
776 
777 		assertEquals(200, service.getStatus());
778 		assertEquals("application/x-git-upload-pack-result",
779 				service.getResponseHeader(HDR_CONTENT_TYPE));
780 	}
781 
782 	@Test
783 	public void testInitialClone_Redirect301Small() throws Exception {
784 		initialClone_Redirect(1, 301);
785 	}
786 
787 	@Test
788 	public void testInitialClone_Redirect301Local() throws Exception {
789 		initialClone_Redirect(1, 301, true);
790 	}
791 
792 	@Test
793 	public void testInitialClone_Redirect302Small() throws Exception {
794 		initialClone_Redirect(1, 302);
795 	}
796 
797 	@Test
798 	public void testInitialClone_Redirect303Small() throws Exception {
799 		initialClone_Redirect(1, 303);
800 	}
801 
802 	@Test
803 	public void testInitialClone_Redirect307Small() throws Exception {
804 		initialClone_Redirect(1, 307);
805 	}
806 
807 	@Test
808 	public void testInitialClone_RedirectMultiple() throws Exception {
809 		initialClone_Redirect(4, 302);
810 	}
811 
812 	@Test
813 	public void testInitialClone_RedirectMax() throws Exception {
814 		StoredConfig userConfig = SystemReader.getInstance()
815 				.getUserConfig();
816 		userConfig.setInt("http", null, "maxRedirects", 4);
817 		userConfig.save();
818 		initialClone_Redirect(4, 302);
819 	}
820 
821 	@Test
822 	public void testInitialClone_RedirectTooOften() throws Exception {
823 		StoredConfig userConfig = SystemReader.getInstance()
824 				.getUserConfig();
825 		userConfig.setInt("http", null, "maxRedirects", 3);
826 		userConfig.save();
827 
828 		URIish cloneFrom = extendPath(redirectURI, "/response/4/302");
829 		String remoteUri = cloneFrom.toString();
830 		try (Repository dst = createBareRepository();
831 				Transport t = Transport.open(dst, cloneFrom)) {
832 			assertFalse(dst.getObjectDatabase().has(A_txt));
833 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
834 			fail("Should have failed (too many redirects)");
835 		} catch (TransportException e) {
836 			String expectedMessageBegin = remoteUri.toString() + ": "
837 					+ MessageFormat.format(JGitText.get().redirectLimitExceeded,
838 							"3", remoteUri.replace("/4/", "/1/") + '/', "");
839 			String message = e.getMessage();
840 			if (message.length() > expectedMessageBegin.length()) {
841 				message = message.substring(0, expectedMessageBegin.length());
842 			}
843 			assertEquals(expectedMessageBegin, message);
844 		}
845 	}
846 
847 	@Test
848 	public void testInitialClone_RedirectLoop() throws Exception {
849 		URIish cloneFrom = extendPath(redirectURI, "/loop");
850 		try (Repository dst = createBareRepository();
851 				Transport t = Transport.open(dst, cloneFrom)) {
852 			assertFalse(dst.getObjectDatabase().has(A_txt));
853 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
854 			fail("Should have failed (redirect loop)");
855 		} catch (TransportException e) {
856 			assertTrue(e.getMessage().contains("Redirected more than"));
857 		}
858 	}
859 
860 	@Test
861 	public void testInitialClone_RedirectOnPostAllowed() throws Exception {
862 		StoredConfig userConfig = SystemReader.getInstance()
863 				.getUserConfig();
864 		userConfig.setString("http", null, "followRedirects", "true");
865 		userConfig.save();
866 
867 		URIish cloneFrom = extendPath(remoteURI, "/post");
868 		try (Repository dst = createBareRepository();
869 				Transport t = Transport.open(dst, cloneFrom)) {
870 			assertFalse(dst.getObjectDatabase().has(A_txt));
871 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
872 			assertTrue(dst.getObjectDatabase().has(A_txt));
873 			assertEquals(B, dst.exactRef(master).getObjectId());
874 			fsck(dst, B);
875 		}
876 
877 		List<AccessEvent> requests = getRequests();
878 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
879 
880 		AccessEvent info = requests.get(0);
881 		assertEquals("GET", info.getMethod());
882 		assertEquals(join(cloneFrom, "info/refs"), info.getPath());
883 		assertEquals(1, info.getParameters().size());
884 		assertEquals("git-upload-pack", info.getParameter("service"));
885 		assertEquals(200, info.getStatus());
886 		assertEquals("application/x-git-upload-pack-advertisement",
887 				info.getResponseHeader(HDR_CONTENT_TYPE));
888 		if (!enableProtocolV2) {
889 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
890 		}
891 
892 		AccessEvent redirect = requests.get(1);
893 		assertEquals("POST", redirect.getMethod());
894 		assertEquals(301, redirect.getStatus());
895 
896 		for (int i = 2; i < requests.size(); i++) {
897 			AccessEvent service = requests.get(i);
898 			assertEquals("POST", service.getMethod());
899 			assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
900 			assertEquals(0, service.getParameters().size());
901 			assertNotNull("has content-length",
902 					service.getRequestHeader(HDR_CONTENT_LENGTH));
903 			assertNull("not chunked",
904 					service.getRequestHeader(HDR_TRANSFER_ENCODING));
905 			assertEquals(200, service.getStatus());
906 			assertEquals("application/x-git-upload-pack-result",
907 					service.getResponseHeader(HDR_CONTENT_TYPE));
908 		}
909 	}
910 
911 	@Test
912 	public void testInitialClone_RedirectOnPostForbidden() throws Exception {
913 		URIish cloneFrom = extendPath(remoteURI, "/post");
914 		try (Repository dst = createBareRepository();
915 				Transport t = Transport.open(dst, cloneFrom)) {
916 			assertFalse(dst.getObjectDatabase().has(A_txt));
917 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
918 			fail("Should have failed (redirect on POST)");
919 		} catch (TransportException e) {
920 			assertTrue(e.getMessage().contains("301"));
921 		}
922 		assertLastRequestStatusCode(301);
923 	}
924 
925 	@Test
926 	public void testInitialClone_RedirectForbidden() throws Exception {
927 		StoredConfig userConfig = SystemReader.getInstance()
928 				.getUserConfig();
929 		userConfig.setString("http", null, "followRedirects", "false");
930 		userConfig.save();
931 
932 		try (Repository dst = createBareRepository();
933 				Transport t = Transport.open(dst, redirectURI)) {
934 			assertFalse(dst.getObjectDatabase().has(A_txt));
935 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
936 			fail("Should have failed (redirects forbidden)");
937 		} catch (TransportException e) {
938 			assertTrue(
939 					e.getMessage().contains("http.followRedirects is false"));
940 		}
941 		assertLastRequestStatusCode(301);
942 	}
943 
944 	private void assertFetchRequests(List<AccessEvent> requests, int index) {
945 		AccessEvent info = requests.get(index++);
946 		assertEquals("GET", info.getMethod());
947 		assertEquals(join(authURI, "info/refs"), info.getPath());
948 		assertEquals(1, info.getParameters().size());
949 		assertEquals("git-upload-pack", info.getParameter("service"));
950 		assertEquals(200, info.getStatus());
951 		assertEquals("application/x-git-upload-pack-advertisement",
952 				info.getResponseHeader(HDR_CONTENT_TYPE));
953 		if (!enableProtocolV2) {
954 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
955 		}
956 
957 		for (int i = index; i < requests.size(); i++) {
958 			AccessEvent service = requests.get(i);
959 			assertEquals("POST", service.getMethod());
960 			assertEquals(join(authURI, "git-upload-pack"), service.getPath());
961 			assertEquals(0, service.getParameters().size());
962 			assertNotNull("has content-length",
963 					service.getRequestHeader(HDR_CONTENT_LENGTH));
964 			assertNull("not chunked",
965 					service.getRequestHeader(HDR_TRANSFER_ENCODING));
966 
967 			assertEquals(200, service.getStatus());
968 			assertEquals("application/x-git-upload-pack-result",
969 					service.getResponseHeader(HDR_CONTENT_TYPE));
970 		}
971 	}
972 
973 	@Test
974 	public void testInitialClone_WithAuthentication() throws Exception {
975 		try (Repository dst = createBareRepository();
976 				Transport t = Transport.open(dst, authURI)) {
977 			assertFalse(dst.getObjectDatabase().has(A_txt));
978 			t.setCredentialsProvider(testCredentials);
979 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
980 			assertTrue(dst.getObjectDatabase().has(A_txt));
981 			assertEquals(B, dst.exactRef(master).getObjectId());
982 			fsck(dst, B);
983 		}
984 
985 		List<AccessEvent> requests = getRequests();
986 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
987 
988 		AccessEvent info = requests.get(0);
989 		assertEquals("GET", info.getMethod());
990 		assertEquals(401, info.getStatus());
991 
992 		assertFetchRequests(requests, 1);
993 	}
994 
995 	@Test
996 	public void testInitialClone_WithPreAuthentication() throws Exception {
997 		try (Repository dst = createBareRepository();
998 				Transport t = Transport.open(dst, authURI)) {
999 			assertFalse(dst.getObjectDatabase().has(A_txt));
1000 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1001 					AppServer.username, AppServer.password);
1002 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1003 			assertTrue(dst.getObjectDatabase().has(A_txt));
1004 			assertEquals(B, dst.exactRef(master).getObjectId());
1005 			fsck(dst, B);
1006 		}
1007 
1008 		List<AccessEvent> requests = getRequests();
1009 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
1010 
1011 		assertFetchRequests(requests, 0);
1012 	}
1013 
1014 	@Test
1015 	public void testInitialClone_WithPreAuthenticationCleared()
1016 			throws Exception {
1017 		try (Repository dst = createBareRepository();
1018 				Transport t = Transport.open(dst, authURI)) {
1019 			assertFalse(dst.getObjectDatabase().has(A_txt));
1020 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1021 					AppServer.username, AppServer.password);
1022 			((TransportHttp) t).setPreemptiveBasicAuthentication(null, null);
1023 			t.setCredentialsProvider(testCredentials);
1024 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1025 			assertTrue(dst.getObjectDatabase().has(A_txt));
1026 			assertEquals(B, dst.exactRef(master).getObjectId());
1027 			fsck(dst, B);
1028 		}
1029 
1030 		List<AccessEvent> requests = getRequests();
1031 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
1032 
1033 		AccessEvent info = requests.get(0);
1034 		assertEquals("GET", info.getMethod());
1035 		assertEquals(401, info.getStatus());
1036 
1037 		assertFetchRequests(requests, 1);
1038 	}
1039 
1040 	@Test
1041 	public void testInitialClone_PreAuthenticationTooLate() throws Exception {
1042 		try (Repository dst = createBareRepository();
1043 				Transport t = Transport.open(dst, authURI)) {
1044 			assertFalse(dst.getObjectDatabase().has(A_txt));
1045 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1046 					AppServer.username, AppServer.password);
1047 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1048 			assertTrue(dst.getObjectDatabase().has(A_txt));
1049 			assertEquals(B, dst.exactRef(master).getObjectId());
1050 			fsck(dst, B);
1051 			List<AccessEvent> requests = getRequests();
1052 			assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
1053 			assertFetchRequests(requests, 0);
1054 			assertThrows(IllegalStateException.class,
1055 					() -> ((TransportHttp) t).setPreemptiveBasicAuthentication(
1056 							AppServer.username, AppServer.password));
1057 			assertThrows(IllegalStateException.class, () -> ((TransportHttp) t)
1058 					.setPreemptiveBasicAuthentication(null, null));
1059 		}
1060 	}
1061 
1062 	@Test
1063 	public void testInitialClone_WithWrongPreAuthenticationAndCredentialProvider()
1064 			throws Exception {
1065 		try (Repository dst = createBareRepository();
1066 				Transport t = Transport.open(dst, authURI)) {
1067 			assertFalse(dst.getObjectDatabase().has(A_txt));
1068 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1069 					AppServer.username, AppServer.password + 'x');
1070 			t.setCredentialsProvider(testCredentials);
1071 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1072 			assertTrue(dst.getObjectDatabase().has(A_txt));
1073 			assertEquals(B, dst.exactRef(master).getObjectId());
1074 			fsck(dst, B);
1075 		}
1076 
1077 		List<AccessEvent> requests = getRequests();
1078 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
1079 
1080 		AccessEvent info = requests.get(0);
1081 		assertEquals("GET", info.getMethod());
1082 		assertEquals(401, info.getStatus());
1083 
1084 		assertFetchRequests(requests, 1);
1085 	}
1086 
1087 	@Test
1088 	public void testInitialClone_WithWrongPreAuthentication() throws Exception {
1089 		try (Repository dst = createBareRepository();
1090 				Transport t = Transport.open(dst, authURI)) {
1091 			assertFalse(dst.getObjectDatabase().has(A_txt));
1092 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1093 					AppServer.username, AppServer.password + 'x');
1094 			TransportException e = assertThrows(TransportException.class,
1095 					() -> t.fetch(NullProgressMonitor.INSTANCE,
1096 							mirror(master)));
1097 			String msg = e.getMessage();
1098 			assertTrue("Unexpected exception message: " + msg,
1099 					msg.contains("no CredentialsProvider"));
1100 		}
1101 		List<AccessEvent> requests = getRequests();
1102 		assertEquals(1, requests.size());
1103 
1104 		AccessEvent info = requests.get(0);
1105 		assertEquals("GET", info.getMethod());
1106 		assertEquals(401, info.getStatus());
1107 	}
1108 
1109 	@Test
1110 	public void testInitialClone_WithUserInfo() throws Exception {
1111 		URIish withUserInfo = authURI.setUser(AppServer.username)
1112 				.setPass(AppServer.password);
1113 		try (Repository dst = createBareRepository();
1114 				Transport t = Transport.open(dst, withUserInfo)) {
1115 			assertFalse(dst.getObjectDatabase().has(A_txt));
1116 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1117 			assertTrue(dst.getObjectDatabase().has(A_txt));
1118 			assertEquals(B, dst.exactRef(master).getObjectId());
1119 			fsck(dst, B);
1120 		}
1121 
1122 		List<AccessEvent> requests = getRequests();
1123 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
1124 
1125 		assertFetchRequests(requests, 0);
1126 	}
1127 
1128 	@Test
1129 	public void testInitialClone_PreAuthOverridesUserInfo() throws Exception {
1130 		URIish withUserInfo = authURI.setUser(AppServer.username)
1131 				.setPass(AppServer.password + 'x');
1132 		try (Repository dst = createBareRepository();
1133 				Transport t = Transport.open(dst, withUserInfo)) {
1134 			assertFalse(dst.getObjectDatabase().has(A_txt));
1135 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1136 					AppServer.username, AppServer.password);
1137 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1138 			assertTrue(dst.getObjectDatabase().has(A_txt));
1139 			assertEquals(B, dst.exactRef(master).getObjectId());
1140 			fsck(dst, B);
1141 		}
1142 
1143 		List<AccessEvent> requests = getRequests();
1144 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
1145 
1146 		assertFetchRequests(requests, 0);
1147 	}
1148 
1149 	@Test
1150 	public void testInitialClone_WithAuthenticationNoCredentials()
1151 			throws Exception {
1152 		try (Repository dst = createBareRepository();
1153 				Transport t = Transport.open(dst, authURI)) {
1154 			assertFalse(dst.getObjectDatabase().has(A_txt));
1155 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1156 			fail("Should not have succeeded -- no authentication");
1157 		} catch (TransportException e) {
1158 			String msg = e.getMessage();
1159 			assertTrue("Unexpected exception message: " + msg,
1160 					msg.contains("no CredentialsProvider"));
1161 		}
1162 		List<AccessEvent> requests = getRequests();
1163 		assertEquals(1, requests.size());
1164 
1165 		AccessEvent info = requests.get(0);
1166 		assertEquals("GET", info.getMethod());
1167 		assertEquals(401, info.getStatus());
1168 	}
1169 
1170 	@Test
1171 	public void testInitialClone_WithAuthenticationWrongCredentials()
1172 			throws Exception {
1173 		try (Repository dst = createBareRepository();
1174 				Transport t = Transport.open(dst, authURI)) {
1175 			assertFalse(dst.getObjectDatabase().has(A_txt));
1176 			t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
1177 					AppServer.username, "wrongpassword"));
1178 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1179 			fail("Should not have succeeded -- wrong password");
1180 		} catch (TransportException e) {
1181 			String msg = e.getMessage();
1182 			assertTrue("Unexpected exception message: " + msg,
1183 					msg.contains("auth"));
1184 		}
1185 		List<AccessEvent> requests = getRequests();
1186 		// Once without authentication plus three re-tries with authentication
1187 		assertEquals(4, requests.size());
1188 
1189 		for (AccessEvent event : requests) {
1190 			assertEquals("GET", event.getMethod());
1191 			assertEquals(401, event.getStatus());
1192 		}
1193 	}
1194 
1195 	@Test
1196 	public void testInitialClone_WithAuthenticationAfterRedirect()
1197 			throws Exception {
1198 		URIish cloneFrom = extendPath(redirectURI, "/target/auth");
1199 		CredentialsProvider uriSpecificCredentialsProvider = new UsernamePasswordCredentialsProvider(
1200 				"unknown", "none") {
1201 			@Override
1202 			public boolean get(URIish uri, CredentialItem... items)
1203 					throws UnsupportedCredentialItem {
1204 				// Only return the true credentials if the uri path starts with
1205 				// /auth. This ensures that we do provide the correct
1206 				// credentials only for the URi after the redirect, making the
1207 				// test fail if we should be asked for the credentials for the
1208 				// original URI.
1209 				if (uri.getPath().startsWith("/auth")) {
1210 					return testCredentials.get(uri, items);
1211 				}
1212 				return super.get(uri, items);
1213 			}
1214 		};
1215 		try (Repository dst = createBareRepository();
1216 				Transport t = Transport.open(dst, cloneFrom)) {
1217 			assertFalse(dst.getObjectDatabase().has(A_txt));
1218 			t.setCredentialsProvider(uriSpecificCredentialsProvider);
1219 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1220 			assertTrue(dst.getObjectDatabase().has(A_txt));
1221 			assertEquals(B, dst.exactRef(master).getObjectId());
1222 			fsck(dst, B);
1223 		}
1224 
1225 		List<AccessEvent> requests = getRequests();
1226 		assertEquals(enableProtocolV2 ? 5 : 4, requests.size());
1227 
1228 		int requestNumber = 0;
1229 		AccessEvent redirect = requests.get(requestNumber++);
1230 		assertEquals("GET", redirect.getMethod());
1231 		assertEquals(join(cloneFrom, "info/refs"), redirect.getPath());
1232 		assertEquals(301, redirect.getStatus());
1233 
1234 		AccessEvent info = requests.get(requestNumber++);
1235 		assertEquals("GET", info.getMethod());
1236 		assertEquals(join(authURI, "info/refs"), info.getPath());
1237 		assertEquals(401, info.getStatus());
1238 
1239 		info = requests.get(requestNumber++);
1240 		assertEquals("GET", info.getMethod());
1241 		assertEquals(join(authURI, "info/refs"), info.getPath());
1242 		assertEquals(1, info.getParameters().size());
1243 		assertEquals("git-upload-pack", info.getParameter("service"));
1244 		assertEquals(200, info.getStatus());
1245 		assertEquals("application/x-git-upload-pack-advertisement",
1246 				info.getResponseHeader(HDR_CONTENT_TYPE));
1247 		if (!enableProtocolV2) {
1248 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
1249 		} else {
1250 			AccessEvent lsRefs = requests.get(requestNumber++);
1251 			assertEquals("POST", lsRefs.getMethod());
1252 			assertEquals(join(authURI, "git-upload-pack"), lsRefs.getPath());
1253 			assertEquals(0, lsRefs.getParameters().size());
1254 			assertNotNull("has content-length",
1255 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
1256 			assertNull("not chunked",
1257 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
1258 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
1259 			assertEquals(200, lsRefs.getStatus());
1260 			assertEquals("application/x-git-upload-pack-result",
1261 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
1262 		}
1263 
1264 		AccessEvent service = requests.get(requestNumber);
1265 		assertEquals("POST", service.getMethod());
1266 		assertEquals(join(authURI, "git-upload-pack"), service.getPath());
1267 		assertEquals(0, service.getParameters().size());
1268 		assertNotNull("has content-length",
1269 				service.getRequestHeader(HDR_CONTENT_LENGTH));
1270 		assertNull("not chunked",
1271 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
1272 
1273 		assertEquals(200, service.getStatus());
1274 		assertEquals("application/x-git-upload-pack-result",
1275 				service.getResponseHeader(HDR_CONTENT_TYPE));
1276 	}
1277 
1278 	@Test
1279 	public void testInitialClone_WithAuthenticationOnPostOnly()
1280 			throws Exception {
1281 		try (Repository dst = createBareRepository();
1282 				Transport t = Transport.open(dst, authOnPostURI)) {
1283 			assertFalse(dst.getObjectDatabase().has(A_txt));
1284 			t.setCredentialsProvider(testCredentials);
1285 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1286 			assertTrue(dst.getObjectDatabase().has(A_txt));
1287 			assertEquals(B, dst.exactRef(master).getObjectId());
1288 			fsck(dst, B);
1289 		}
1290 
1291 		List<AccessEvent> requests = getRequests();
1292 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
1293 
1294 		AccessEvent info = requests.get(0);
1295 		assertEquals("GET", info.getMethod());
1296 		assertEquals(join(authOnPostURI, "info/refs"), info.getPath());
1297 		assertEquals(1, info.getParameters().size());
1298 		assertEquals("git-upload-pack", info.getParameter("service"));
1299 		assertEquals(200, info.getStatus());
1300 		assertEquals("application/x-git-upload-pack-advertisement",
1301 				info.getResponseHeader(HDR_CONTENT_TYPE));
1302 		if (!enableProtocolV2) {
1303 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
1304 		}
1305 
1306 		AccessEvent service = requests.get(1);
1307 		assertEquals("POST", service.getMethod());
1308 		assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath());
1309 		assertEquals(401, service.getStatus());
1310 
1311 		for (int i = 2; i < requests.size(); i++) {
1312 			service = requests.get(i);
1313 			assertEquals("POST", service.getMethod());
1314 			assertEquals(join(authOnPostURI, "git-upload-pack"),
1315 					service.getPath());
1316 			assertEquals(0, service.getParameters().size());
1317 			assertNotNull("has content-length",
1318 					service.getRequestHeader(HDR_CONTENT_LENGTH));
1319 			assertNull("not chunked",
1320 					service.getRequestHeader(HDR_TRANSFER_ENCODING));
1321 
1322 			assertEquals(200, service.getStatus());
1323 			assertEquals("application/x-git-upload-pack-result",
1324 					service.getResponseHeader(HDR_CONTENT_TYPE));
1325 		}
1326 	}
1327 
1328 	@Test
1329 	public void testFetch_FewLocalCommits() throws Exception {
1330 		// Bootstrap by doing the clone.
1331 		//
1332 		TestRepository dst = createTestRepository();
1333 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1334 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1335 		}
1336 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
1337 		List<AccessEvent> cloneRequests = getRequests();
1338 
1339 		// Only create a few new commits.
1340 		TestRepository.BranchBuilder b = dst.branch(master);
1341 		for (int i = 0; i < 4; i++)
1342 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
1343 
1344 		// Create a new commit on the remote.
1345 		//
1346 		RevCommit Z;
1347 		try (TestRepository<Repository> tr = new TestRepository<>(
1348 				remoteRepository)) {
1349 			b = tr.branch(master);
1350 			Z = b.commit().message("Z").create();
1351 		}
1352 
1353 		// Now incrementally update.
1354 		//
1355 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1356 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1357 		}
1358 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
1359 
1360 		List<AccessEvent> requests = getRequests();
1361 		requests.removeAll(cloneRequests);
1362 
1363 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
1364 
1365 		int requestNumber = 0;
1366 		AccessEvent info = requests.get(requestNumber++);
1367 		assertEquals("GET", info.getMethod());
1368 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1369 		assertEquals(1, info.getParameters().size());
1370 		assertEquals("git-upload-pack", info.getParameter("service"));
1371 		assertEquals(200, info.getStatus());
1372 		assertEquals("application/x-git-upload-pack-advertisement",
1373 				info.getResponseHeader(HDR_CONTENT_TYPE));
1374 
1375 		if (enableProtocolV2) {
1376 			AccessEvent lsRefs = requests.get(requestNumber++);
1377 			assertEquals("POST", lsRefs.getMethod());
1378 			assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
1379 			assertEquals(0, lsRefs.getParameters().size());
1380 			assertNotNull("has content-length",
1381 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
1382 			assertNull("not chunked",
1383 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
1384 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
1385 			assertEquals(200, lsRefs.getStatus());
1386 			assertEquals("application/x-git-upload-pack-result",
1387 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
1388 		}
1389 
1390 		// We should have needed one request to perform the fetch.
1391 		//
1392 		AccessEvent service = requests.get(requestNumber);
1393 		assertEquals("POST", service.getMethod());
1394 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
1395 		assertEquals(0, service.getParameters().size());
1396 		assertNotNull("has content-length",
1397 				service.getRequestHeader(HDR_CONTENT_LENGTH));
1398 		assertNull("not chunked",
1399 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
1400 
1401 		assertEquals(200, service.getStatus());
1402 		assertEquals("application/x-git-upload-pack-result",
1403 				service.getResponseHeader(HDR_CONTENT_TYPE));
1404 	}
1405 
1406 	@Test
1407 	public void testFetch_TooManyLocalCommits() throws Exception {
1408 		// Bootstrap by doing the clone.
1409 		//
1410 		TestRepository dst = createTestRepository();
1411 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1412 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1413 		}
1414 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
1415 		List<AccessEvent> cloneRequests = getRequests();
1416 
1417 		// Force enough into the local client that enumeration will
1418 		// need multiple packets, but not too many to overflow and
1419 		// not pick up the ACK_COMMON message.
1420 		//
1421 		TestRepository.BranchBuilder b = dst.branch(master);
1422 		for (int i = 0; i < 32 - 1; i++)
1423 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
1424 
1425 		// Create a new commit on the remote.
1426 		//
1427 		RevCommit Z;
1428 		try (TestRepository<Repository> tr = new TestRepository<>(
1429 				remoteRepository)) {
1430 			b = tr.branch(master);
1431 			Z = b.commit().message("Z").create();
1432 		}
1433 
1434 		// Now incrementally update.
1435 		//
1436 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1437 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1438 		}
1439 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
1440 
1441 		List<AccessEvent> requests = getRequests();
1442 		requests.removeAll(cloneRequests);
1443 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
1444 
1445 		int requestNumber = 0;
1446 		AccessEvent info = requests.get(requestNumber++);
1447 		assertEquals("GET", info.getMethod());
1448 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1449 		assertEquals(1, info.getParameters().size());
1450 		assertEquals("git-upload-pack", info.getParameter("service"));
1451 		assertEquals(200, info.getStatus());
1452 		assertEquals("application/x-git-upload-pack-advertisement", info
1453 				.getResponseHeader(HDR_CONTENT_TYPE));
1454 
1455 		if (enableProtocolV2) {
1456 			AccessEvent lsRefs = requests.get(requestNumber++);
1457 			assertEquals("POST", lsRefs.getMethod());
1458 			assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
1459 			assertEquals(0, lsRefs.getParameters().size());
1460 			assertNotNull("has content-length",
1461 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
1462 			assertNull("not chunked",
1463 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
1464 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
1465 			assertEquals(200, lsRefs.getStatus());
1466 			assertEquals("application/x-git-upload-pack-result",
1467 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
1468 		}
1469 
1470 		// We should have needed two requests to perform the fetch
1471 		// due to the high number of local unknown commits.
1472 		//
1473 		AccessEvent service = requests.get(requestNumber++);
1474 		assertEquals("POST", service.getMethod());
1475 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
1476 		assertEquals(0, service.getParameters().size());
1477 		assertNotNull("has content-length", service
1478 				.getRequestHeader(HDR_CONTENT_LENGTH));
1479 		assertNull("not chunked", service
1480 				.getRequestHeader(HDR_TRANSFER_ENCODING));
1481 
1482 		assertEquals(200, service.getStatus());
1483 		assertEquals("application/x-git-upload-pack-result", service
1484 				.getResponseHeader(HDR_CONTENT_TYPE));
1485 
1486 		service = requests.get(requestNumber);
1487 		assertEquals("POST", service.getMethod());
1488 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
1489 		assertEquals(0, service.getParameters().size());
1490 		assertNotNull("has content-length", service
1491 				.getRequestHeader(HDR_CONTENT_LENGTH));
1492 		assertNull("not chunked", service
1493 				.getRequestHeader(HDR_TRANSFER_ENCODING));
1494 
1495 		assertEquals(200, service.getStatus());
1496 		assertEquals("application/x-git-upload-pack-result", service
1497 				.getResponseHeader(HDR_CONTENT_TYPE));
1498 	}
1499 
1500 	@Test
1501 	public void testFetch_MaxHavesCutoffAfterAckOnly() throws Exception {
1502 		// Bootstrap by doing the clone.
1503 		//
1504 		TestRepository dst = createTestRepository();
1505 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1506 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1507 		}
1508 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
1509 
1510 		// Force enough into the local client that enumeration will
1511 		// need more than MAX_HAVES (256) haves to be sent. The server
1512 		// doesn't know any of these, so it will never ACK. The client
1513 		// should keep going.
1514 		//
1515 		// If it does, client and server will find a common commit, and
1516 		// the pack file will contain exactly the one commit object Z
1517 		// we create on the remote, which we can test for via the progress
1518 		// monitor, which should have something like
1519 		// "Receiving objects: 100% (1/1)". If the client sends a "done"
1520 		// too early, the server will send more objects, and we'll have
1521 		// a line like "Receiving objects: 100% (8/8)".
1522 		TestRepository.BranchBuilder b = dst.branch(master);
1523 		// The client will send 32 + 64 + 128 + 256 + 512 haves. Only the
1524 		// last one will be a common commit. If the cutoff kicks in too
1525 		// early (after 480), we'll get too many objects in the fetch.
1526 		for (int i = 0; i < 992; i++)
1527 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
1528 
1529 		// Create a new commit on the remote.
1530 		//
1531 		RevCommit Z;
1532 		try (TestRepository<Repository> tr = new TestRepository<>(
1533 				remoteRepository)) {
1534 			b = tr.branch(master);
1535 			Z = b.commit().message("Z").create();
1536 		}
1537 
1538 		// Now incrementally update.
1539 		//
1540 		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1541 		Writer writer = new OutputStreamWriter(buffer, StandardCharsets.UTF_8);
1542 		TextProgressMonitor monitor = new TextProgressMonitor(writer);
1543 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1544 			t.fetch(monitor, mirror(master));
1545 		}
1546 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
1547 
1548 		String progressMessages = new String(buffer.toByteArray(),
1549 				StandardCharsets.UTF_8);
1550 		Pattern expected = Pattern
1551 				.compile("Receiving objects:\\s+100% \\(1/1\\)\n");
1552 		if (!expected.matcher(progressMessages).find()) {
1553 			System.out.println(progressMessages);
1554 			fail("Expected only one object to be sent");
1555 		}
1556 	}
1557 
1558 	@Test
1559 	public void testInitialClone_BrokenServer() throws Exception {
1560 		try (Repository dst = createBareRepository();
1561 				Transport t = Transport.open(dst, brokenURI)) {
1562 			assertFalse(dst.getObjectDatabase().has(A_txt));
1563 			try {
1564 				t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1565 				fail("fetch completed despite upload-pack being broken");
1566 			} catch (TransportException err) {
1567 				String exp = brokenURI + ": expected"
1568 						+ " Content-Type application/x-git-upload-pack-result;"
1569 						+ " received Content-Type text/plain;charset=utf-8";
1570 				assertEquals(exp, err.getMessage());
1571 			}
1572 		}
1573 
1574 		List<AccessEvent> requests = getRequests();
1575 		assertEquals(2, requests.size());
1576 
1577 		AccessEvent info = requests.get(0);
1578 		assertEquals("GET", info.getMethod());
1579 		assertEquals(join(brokenURI, "info/refs"), info.getPath());
1580 		assertEquals(1, info.getParameters().size());
1581 		assertEquals("git-upload-pack", info.getParameter("service"));
1582 		assertEquals(200, info.getStatus());
1583 		assertEquals("application/x-git-upload-pack-advertisement", info
1584 				.getResponseHeader(HDR_CONTENT_TYPE));
1585 
1586 		AccessEvent service = requests.get(1);
1587 		assertEquals("POST", service.getMethod());
1588 		assertEquals(join(brokenURI, "git-upload-pack"), service.getPath());
1589 		assertEquals(0, service.getParameters().size());
1590 		assertEquals(200, service.getStatus());
1591 		assertEquals("text/plain;charset=utf-8",
1592 				service.getResponseHeader(HDR_CONTENT_TYPE));
1593 	}
1594 
1595 	@Test
1596 	public void testInvalidWant() throws Exception {
1597 		ObjectId id;
1598 		try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) {
1599 			id = formatter.idFor(Constants.OBJ_BLOB,
1600 					"testInvalidWant".getBytes(UTF_8));
1601 		}
1602 
1603 		try (Repository dst = createBareRepository();
1604 				Transport t = Transport.open(dst, remoteURI);
1605 				FetchConnection c = t.openFetch()) {
1606 			Ref want = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(),
1607 					id);
1608 			c.fetch(NullProgressMonitor.INSTANCE, Collections.singleton(want),
1609 					Collections.<ObjectId> emptySet());
1610 			fail("Server accepted want " + id.name());
1611 		} catch (TransportException err) {
1612 			assertTrue(err.getMessage()
1613 					.contains("Bad Request"));
1614 		}
1615 		assertLastRequestStatusCode(400);
1616 	}
1617 
1618 	@Test
1619 	public void testFetch_RefsUnreadableOnUpload() throws Exception {
1620 		AppServer noRefServer = new AppServer();
1621 		try {
1622 			final String repoName = "refs-unreadable";
1623 			RefsUnreadableInMemoryRepository badRefsRepo = new RefsUnreadableInMemoryRepository(
1624 					new DfsRepositoryDescription(repoName));
1625 			final TestRepository<Repository> repo = new TestRepository<>(
1626 					badRefsRepo);
1627 			badRefsRepo.getConfig().setInt("protocol", null, "version",
1628 					enableProtocolV2 ? 2 : 0);
1629 
1630 			ServletContextHandler app = noRefServer.addContext("/git");
1631 			GitServlet gs = new GitServlet();
1632 			gs.setRepositoryResolver(new TestRepositoryResolver(repo, repoName));
1633 			app.addServlet(new ServletHolder(gs), "/*");
1634 			noRefServer.setUp();
1635 
1636 			RevBlob A2_txt = repo.blob("A2");
1637 			RevCommit A2 = repo.commit().add("A2_txt", A2_txt).create();
1638 			RevCommit B2 = repo.commit().parent(A2).add("A2_txt", "C2")
1639 					.add("B2", "B2").create();
1640 			repo.update(master, B2);
1641 
1642 			URIish badRefsURI = new URIish(noRefServer.getURI()
1643 					.resolve(app.getContextPath() + "/" + repoName).toString());
1644 
1645 			try (Repository dst = createBareRepository();
1646 					Transport t = Transport.open(dst, badRefsURI);
1647 					FetchConnection c = t.openFetch()) {
1648 				// We start failing here to exercise the post-advertisement
1649 				// upload pack handler.
1650 				badRefsRepo.startFailing();
1651 				// Need to flush caches because ref advertisement populated them.
1652 				badRefsRepo.getRefDatabase().refresh();
1653 				c.fetch(NullProgressMonitor.INSTANCE,
1654 						Collections.singleton(c.getRef(master)),
1655 						Collections.<ObjectId> emptySet());
1656 				fail("Successfully served ref with value " + c.getRef(master));
1657 			} catch (TransportException err) {
1658 				assertTrue("Unexpected exception message " + err.getMessage(),
1659 						err.getMessage().contains("Server Error"));
1660 			}
1661 		} finally {
1662 			noRefServer.tearDown();
1663 		}
1664 	}
1665 
1666 	@Test
1667 	public void testPush_NotAuthorized() throws Exception {
1668 		final TestRepository src = createTestRepository();
1669 		final RevBlob Q_txt = src.blob("new text");
1670 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
1671 		final Repository db = src.getRepository();
1672 		final String dstName = Constants.R_HEADS + "new.branch";
1673 
1674 		// push anonymous shouldn't be allowed.
1675 		//
1676 		try (Transport t = Transport.open(db, remoteURI)) {
1677 			final String srcExpr = Q.name();
1678 			final boolean forceUpdate = false;
1679 			final String localName = null;
1680 			final ObjectId oldId = null;
1681 
1682 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
1683 					srcExpr, dstName, forceUpdate, localName, oldId);
1684 			try {
1685 				t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
1686 				fail("anonymous push incorrectly accepted without error");
1687 			} catch (TransportException e) {
1688 				final String exp = remoteURI + ": "
1689 						+ JGitText.get().authenticationNotSupported;
1690 				assertEquals(exp, e.getMessage());
1691 			}
1692 		}
1693 
1694 		List<AccessEvent> requests = getRequests();
1695 		assertEquals(1, requests.size());
1696 
1697 		AccessEvent info = requests.get(0);
1698 		assertEquals("GET", info.getMethod());
1699 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1700 		assertEquals(1, info.getParameters().size());
1701 		assertEquals("git-receive-pack", info.getParameter("service"));
1702 		assertEquals(401, info.getStatus());
1703 	}
1704 
1705 	@Test
1706 	public void testPush_CreateBranch() throws Exception {
1707 		final TestRepository src = createTestRepository();
1708 		final RevBlob Q_txt = src.blob("new text");
1709 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
1710 		final Repository db = src.getRepository();
1711 		final String dstName = Constants.R_HEADS + "new.branch";
1712 
1713 		enableReceivePack();
1714 
1715 		try (Transport t = Transport.open(db, remoteURI)) {
1716 			final String srcExpr = Q.name();
1717 			final boolean forceUpdate = false;
1718 			final String localName = null;
1719 			final ObjectId oldId = null;
1720 
1721 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
1722 					srcExpr, dstName, forceUpdate, localName, oldId);
1723 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
1724 		}
1725 
1726 		assertTrue(remoteRepository.getObjectDatabase().has(Q_txt));
1727 		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
1728 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
1729 		fsck(remoteRepository, Q);
1730 
1731 		final ReflogReader log = remoteRepository.getReflogReader(dstName);
1732 		assertNotNull("has log for " + dstName, log);
1733 
1734 		final ReflogEntry last = log.getLastEntry();
1735 		assertNotNull("has last entry", last);
1736 		assertEquals(ObjectId.zeroId(), last.getOldId());
1737 		assertEquals(Q, last.getNewId());
1738 		assertEquals("anonymous", last.getWho().getName());
1739 
1740 		// Assumption: The host name we use to contact the server should
1741 		// be the server's own host name, because it should be the loopback
1742 		// network interface.
1743 		//
1744 		final String clientHost = remoteURI.getHost();
1745 		assertEquals("anonymous@" + clientHost, last.getWho().getEmailAddress());
1746 		assertEquals("push: created", last.getComment());
1747 
1748 		List<AccessEvent> requests = getRequests();
1749 		assertEquals(2, requests.size());
1750 
1751 		AccessEvent info = requests.get(0);
1752 		assertEquals("GET", info.getMethod());
1753 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1754 		assertEquals(1, info.getParameters().size());
1755 		assertEquals("git-receive-pack", info.getParameter("service"));
1756 		assertEquals(200, info.getStatus());
1757 		assertEquals("application/x-git-receive-pack-advertisement", info
1758 				.getResponseHeader(HDR_CONTENT_TYPE));
1759 
1760 		AccessEvent service = requests.get(1);
1761 		assertEquals("POST", service.getMethod());
1762 		assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
1763 		assertEquals(0, service.getParameters().size());
1764 		assertNotNull("has content-length", service
1765 				.getRequestHeader(HDR_CONTENT_LENGTH));
1766 		assertNull("not chunked", service
1767 				.getRequestHeader(HDR_TRANSFER_ENCODING));
1768 
1769 		assertEquals(200, service.getStatus());
1770 		assertEquals("application/x-git-receive-pack-result", service
1771 				.getResponseHeader(HDR_CONTENT_TYPE));
1772 	}
1773 
1774 	@Test
1775 	public void testPush_ChunkedEncoding() throws Exception {
1776 		final TestRepository<Repository> src = createTestRepository();
1777 		final RevBlob Q_bin = src.blob(new TestRng("Q").nextBytes(128 * 1024));
1778 		final RevCommit Q = src.commit().add("Q", Q_bin).create();
1779 		final Repository db = src.getRepository();
1780 		final String dstName = Constants.R_HEADS + "new.branch";
1781 
1782 		enableReceivePack();
1783 
1784 		final StoredConfig cfg = db.getConfig();
1785 		cfg.setInt("core", null, "compression", 0);
1786 		cfg.setInt("http", null, "postbuffer", 8 * 1024);
1787 		cfg.save();
1788 
1789 		try (Transport t = Transport.open(db, remoteURI)) {
1790 			final String srcExpr = Q.name();
1791 			final boolean forceUpdate = false;
1792 			final String localName = null;
1793 			final ObjectId oldId = null;
1794 
1795 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
1796 					srcExpr, dstName, forceUpdate, localName, oldId);
1797 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
1798 		}
1799 
1800 		assertTrue(remoteRepository.getObjectDatabase().has(Q_bin));
1801 		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
1802 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
1803 		fsck(remoteRepository, Q);
1804 
1805 		List<AccessEvent> requests = getRequests();
1806 		assertEquals(2, requests.size());
1807 
1808 		AccessEvent info = requests.get(0);
1809 		assertEquals("GET", info.getMethod());
1810 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1811 		assertEquals(1, info.getParameters().size());
1812 		assertEquals("git-receive-pack", info.getParameter("service"));
1813 		assertEquals(200, info.getStatus());
1814 		assertEquals("application/x-git-receive-pack-advertisement", info
1815 				.getResponseHeader(HDR_CONTENT_TYPE));
1816 
1817 		AccessEvent service = requests.get(1);
1818 		assertEquals("POST", service.getMethod());
1819 		assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
1820 		assertEquals(0, service.getParameters().size());
1821 		assertNull("no content-length", service
1822 				.getRequestHeader(HDR_CONTENT_LENGTH));
1823 		assertEquals("chunked", service.getRequestHeader(HDR_TRANSFER_ENCODING));
1824 
1825 		assertEquals(200, service.getStatus());
1826 		assertEquals("application/x-git-receive-pack-result", service
1827 				.getResponseHeader(HDR_CONTENT_TYPE));
1828 	}
1829 
1830 	private void assertLastRequestStatusCode(int statusCode) {
1831 		List<AccessEvent> requests = getRequests();
1832 		assertEquals(statusCode, requests.get(requests.size() - 1).getStatus());
1833 	}
1834 
1835 	private void enableReceivePack() throws IOException {
1836 		final StoredConfig cfg = remoteRepository.getConfig();
1837 		cfg.setBoolean("http", null, "receivepack", true);
1838 		cfg.save();
1839 	}
1840 }