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.RemoteRepositoryException;
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 (RemoteRepositoryException notFound) {
500 				assertEquals(uri + ": Git repository not found",
501 						notFound.getMessage());
502 			}
503 		}
504 
505 		List<AccessEvent> requests = getRequests();
506 		assertEquals(1, requests.size());
507 
508 		AccessEvent info = requests.get(0);
509 		assertEquals("GET", info.getMethod());
510 		assertEquals(join(uri, "info/refs"), info.getPath());
511 		assertEquals(1, info.getParameters().size());
512 		assertEquals("git-upload-pack", info.getParameter("service"));
513 		assertEquals(200, info.getStatus());
514 		assertEquals("application/x-git-upload-pack-advertisement",
515 				info.getResponseHeader(HDR_CONTENT_TYPE));
516 	}
517 
518 	@Test
519 	public void testFetchBySHA1() throws Exception {
520 		try (Repository dst = createBareRepository();
521 				Transport t = Transport.open(dst, remoteURI)) {
522 			assertFalse(dst.getObjectDatabase().has(A_txt));
523 			t.fetch(NullProgressMonitor.INSTANCE,
524 					Collections.singletonList(new RefSpec(B.name())));
525 			assertTrue(dst.getObjectDatabase().has(A_txt));
526 		}
527 	}
528 
529 	@Test
530 	public void testFetchBySHA1Unreachable() throws Exception {
531 		try (Repository dst = createBareRepository();
532 				Transport t = Transport.open(dst, remoteURI)) {
533 			assertFalse(dst.getObjectDatabase().has(A_txt));
534 			Exception e = assertThrows(TransportException.class,
535 					() -> t.fetch(NullProgressMonitor.INSTANCE,
536 							Collections.singletonList(
537 									new RefSpec(unreachableCommit.name()))));
538 			assertTrue(e.getMessage().contains(
539 					"want " + unreachableCommit.name() + " not valid"));
540 		}
541 	}
542 
543 	@Test
544 	public void testFetchBySHA1UnreachableByAdvertiseRefsHook()
545 			throws Exception {
546 		advertiseRefsHook = new AbstractAdvertiseRefsHook() {
547 			@Override
548 			protected Map<String, Ref> getAdvertisedRefs(Repository repository,
549 					RevWalk revWalk) {
550 				return Collections.emptyMap();
551 			}
552 		};
553 
554 		try (Repository dst = createBareRepository();
555 				Transport t = Transport.open(dst, remoteURI)) {
556 			assertFalse(dst.getObjectDatabase().has(A_txt));
557 			Exception e = assertThrows(TransportException.class,
558 					() -> t.fetch(NullProgressMonitor.INSTANCE,
559 							Collections.singletonList(new RefSpec(A.name()))));
560 			assertTrue(
561 					e.getMessage().contains("want " + A.name() + " not valid"));
562 		}
563 	}
564 
565 	@Test
566 	public void testTimeoutExpired() throws Exception {
567 		try (Repository dst = createBareRepository();
568 				Transport t = Transport.open(dst, slowURI)) {
569 			t.setTimeout(1);
570 			TransportException expected = assertThrows(TransportException.class,
571 					() -> t.fetch(NullProgressMonitor.INSTANCE,
572 							mirror(master)));
573 			assertTrue("Unexpected exception message: " + expected.toString(),
574 					expected.getMessage().contains("time"));
575 		}
576 	}
577 
578 	@Test
579 	public void testTimeoutExpiredWithAuth() throws Exception {
580 		try (Repository dst = createBareRepository();
581 				Transport t = Transport.open(dst, slowAuthURI)) {
582 			t.setTimeout(1);
583 			t.setCredentialsProvider(testCredentials);
584 			TransportException expected = assertThrows(TransportException.class,
585 					() -> t.fetch(NullProgressMonitor.INSTANCE,
586 							mirror(master)));
587 			assertTrue("Unexpected exception message: " + expected.toString(),
588 					expected.getMessage().contains("time"));
589 			assertFalse("Unexpected exception message: " + expected.toString(),
590 					expected.getMessage().contains("auth"));
591 		}
592 	}
593 
594 	@Test
595 	public void testInitialClone_Small() throws Exception {
596 		try (Repository dst = createBareRepository();
597 				Transport t = Transport.open(dst, remoteURI)) {
598 			assertFalse(dst.getObjectDatabase().has(A_txt));
599 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
600 			assertTrue(dst.getObjectDatabase().has(A_txt));
601 			assertEquals(B, dst.exactRef(master).getObjectId());
602 			fsck(dst, B);
603 		}
604 
605 		List<AccessEvent> requests = getRequests();
606 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
607 
608 		int requestNumber = 0;
609 		AccessEvent info = requests.get(requestNumber++);
610 		assertEquals("GET", info.getMethod());
611 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
612 		assertEquals(1, info.getParameters().size());
613 		assertEquals("git-upload-pack", info.getParameter("service"));
614 		assertEquals(200, info.getStatus());
615 		assertEquals("application/x-git-upload-pack-advertisement", info
616 				.getResponseHeader(HDR_CONTENT_TYPE));
617 		if (!enableProtocolV2) {
618 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
619 		} else {
620 			AccessEvent lsRefs = requests.get(requestNumber++);
621 			assertEquals("POST", lsRefs.getMethod());
622 			assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
623 			assertEquals(0, lsRefs.getParameters().size());
624 			assertNotNull("has content-length",
625 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
626 			assertNull("not chunked",
627 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
628 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
629 			assertEquals(200, lsRefs.getStatus());
630 			assertEquals("application/x-git-upload-pack-result",
631 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
632 		}
633 
634 		AccessEvent service = requests.get(requestNumber);
635 		assertEquals("POST", service.getMethod());
636 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
637 		assertEquals(0, service.getParameters().size());
638 		assertNotNull("has content-length", service
639 				.getRequestHeader(HDR_CONTENT_LENGTH));
640 		assertNull("not chunked", service
641 				.getRequestHeader(HDR_TRANSFER_ENCODING));
642 
643 		assertEquals(200, service.getStatus());
644 		assertEquals("application/x-git-upload-pack-result", service
645 				.getResponseHeader(HDR_CONTENT_TYPE));
646 	}
647 
648 	@Test
649 	public void test_CloneWithCustomFactory() throws Exception {
650 		HttpConnectionFactory globalFactory = HttpTransport
651 				.getConnectionFactory();
652 		HttpConnectionFactory failingConnectionFactory = new HttpConnectionFactory() {
653 
654 			@Override
655 			public HttpConnection create(URL url) throws IOException {
656 				throw new IOException("Should not be reached");
657 			}
658 
659 			@Override
660 			public HttpConnection create(URL url, Proxy proxy)
661 					throws IOException {
662 				throw new IOException("Should not be reached");
663 			}
664 		};
665 		HttpTransport.setConnectionFactory(failingConnectionFactory);
666 		try {
667 			File tmp = createTempDirectory("cloneViaApi");
668 			boolean[] localFactoryUsed = { false };
669 			TransportConfigCallback callback = new TransportConfigCallback() {
670 
671 				@Override
672 				public void configure(Transport transport) {
673 					if (transport instanceof TransportHttp) {
674 						((TransportHttp) transport).setHttpConnectionFactory(
675 								new HttpConnectionFactory() {
676 
677 									@Override
678 									public HttpConnection create(URL url)
679 											throws IOException {
680 										localFactoryUsed[0] = true;
681 										return globalFactory.create(url);
682 									}
683 
684 									@Override
685 									public HttpConnection create(URL url,
686 											Proxy proxy) throws IOException {
687 										localFactoryUsed[0] = true;
688 										return globalFactory.create(url, proxy);
689 									}
690 								});
691 					}
692 				}
693 			};
694 			try (Git git = Git.cloneRepository().setDirectory(tmp)
695 					.setTransportConfigCallback(callback)
696 					.setURI(remoteURI.toPrivateString()).call()) {
697 				assertTrue("Should have used the local HttpConnectionFactory",
698 						localFactoryUsed[0]);
699 			}
700 		} finally {
701 			HttpTransport.setConnectionFactory(globalFactory);
702 		}
703 	}
704 
705 	private void initialClone_Redirect(int nofRedirects, int code)
706 			throws Exception {
707 		initialClone_Redirect(nofRedirects, code, false);
708 	}
709 
710 	private void initialClone_Redirect(int nofRedirects, int code,
711 			boolean localRedirect) throws Exception {
712 		URIish cloneFrom = redirectURI;
713 		if (localRedirect) {
714 			cloneFrom = extendPath(cloneFrom, "/local");
715 		}
716 		if (code != 301 || nofRedirects > 1) {
717 			cloneFrom = extendPath(cloneFrom,
718 					"/response/" + nofRedirects + "/" + code);
719 		}
720 
721 		try (Repository dst = createBareRepository();
722 				Transport t = Transport.open(dst, cloneFrom)) {
723 			assertFalse(dst.getObjectDatabase().has(A_txt));
724 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
725 			assertTrue(dst.getObjectDatabase().has(A_txt));
726 			assertEquals(B, dst.exactRef(master).getObjectId());
727 			fsck(dst, B);
728 		}
729 
730 		List<AccessEvent> requests = getRequests();
731 		assertEquals((enableProtocolV2 ? 3 : 2) + nofRedirects,
732 				requests.size());
733 
734 		int n = 0;
735 		while (n < nofRedirects) {
736 			AccessEvent redirect = requests.get(n++);
737 			assertEquals(code, redirect.getStatus());
738 		}
739 
740 		AccessEvent info = requests.get(n++);
741 		assertEquals("GET", info.getMethod());
742 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
743 		assertEquals(1, info.getParameters().size());
744 		assertEquals("git-upload-pack", info.getParameter("service"));
745 		assertEquals(200, info.getStatus());
746 		assertEquals("application/x-git-upload-pack-advertisement",
747 				info.getResponseHeader(HDR_CONTENT_TYPE));
748 		if (!enableProtocolV2) {
749 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
750 		} else {
751 			AccessEvent lsRefs = requests.get(n++);
752 			assertEquals("POST", lsRefs.getMethod());
753 			assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
754 			assertEquals(0, lsRefs.getParameters().size());
755 			assertNotNull("has content-length",
756 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
757 			assertNull("not chunked",
758 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
759 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
760 			assertEquals(200, lsRefs.getStatus());
761 			assertEquals("application/x-git-upload-pack-result",
762 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
763 		}
764 
765 		AccessEvent service = requests.get(n++);
766 		assertEquals("POST", service.getMethod());
767 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
768 		assertEquals(0, service.getParameters().size());
769 		assertNotNull("has content-length",
770 				service.getRequestHeader(HDR_CONTENT_LENGTH));
771 		assertNull("not chunked",
772 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
773 
774 		assertEquals(200, service.getStatus());
775 		assertEquals("application/x-git-upload-pack-result",
776 				service.getResponseHeader(HDR_CONTENT_TYPE));
777 	}
778 
779 	@Test
780 	public void testInitialClone_Redirect301Small() throws Exception {
781 		initialClone_Redirect(1, 301);
782 	}
783 
784 	@Test
785 	public void testInitialClone_Redirect301Local() throws Exception {
786 		initialClone_Redirect(1, 301, true);
787 	}
788 
789 	@Test
790 	public void testInitialClone_Redirect302Small() throws Exception {
791 		initialClone_Redirect(1, 302);
792 	}
793 
794 	@Test
795 	public void testInitialClone_Redirect303Small() throws Exception {
796 		initialClone_Redirect(1, 303);
797 	}
798 
799 	@Test
800 	public void testInitialClone_Redirect307Small() throws Exception {
801 		initialClone_Redirect(1, 307);
802 	}
803 
804 	@Test
805 	public void testInitialClone_RedirectMultiple() throws Exception {
806 		initialClone_Redirect(4, 302);
807 	}
808 
809 	@Test
810 	public void testInitialClone_RedirectMax() throws Exception {
811 		StoredConfig userConfig = SystemReader.getInstance()
812 				.getUserConfig();
813 		userConfig.setInt("http", null, "maxRedirects", 4);
814 		userConfig.save();
815 		initialClone_Redirect(4, 302);
816 	}
817 
818 	@Test
819 	public void testInitialClone_RedirectTooOften() throws Exception {
820 		StoredConfig userConfig = SystemReader.getInstance()
821 				.getUserConfig();
822 		userConfig.setInt("http", null, "maxRedirects", 3);
823 		userConfig.save();
824 
825 		URIish cloneFrom = extendPath(redirectURI, "/response/4/302");
826 		String remoteUri = cloneFrom.toString();
827 		try (Repository dst = createBareRepository();
828 				Transport t = Transport.open(dst, cloneFrom)) {
829 			assertFalse(dst.getObjectDatabase().has(A_txt));
830 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
831 			fail("Should have failed (too many redirects)");
832 		} catch (TransportException e) {
833 			String expectedMessageBegin = remoteUri.toString() + ": "
834 					+ MessageFormat.format(JGitText.get().redirectLimitExceeded,
835 							"3", remoteUri.replace("/4/", "/1/") + '/', "");
836 			String message = e.getMessage();
837 			if (message.length() > expectedMessageBegin.length()) {
838 				message = message.substring(0, expectedMessageBegin.length());
839 			}
840 			assertEquals(expectedMessageBegin, message);
841 		}
842 	}
843 
844 	@Test
845 	public void testInitialClone_RedirectLoop() throws Exception {
846 		URIish cloneFrom = extendPath(redirectURI, "/loop");
847 		try (Repository dst = createBareRepository();
848 				Transport t = Transport.open(dst, cloneFrom)) {
849 			assertFalse(dst.getObjectDatabase().has(A_txt));
850 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
851 			fail("Should have failed (redirect loop)");
852 		} catch (TransportException e) {
853 			assertTrue(e.getMessage().contains("Redirected more than"));
854 		}
855 	}
856 
857 	@Test
858 	public void testInitialClone_RedirectOnPostAllowed() throws Exception {
859 		StoredConfig userConfig = SystemReader.getInstance()
860 				.getUserConfig();
861 		userConfig.setString("http", null, "followRedirects", "true");
862 		userConfig.save();
863 
864 		URIish cloneFrom = extendPath(remoteURI, "/post");
865 		try (Repository dst = createBareRepository();
866 				Transport t = Transport.open(dst, cloneFrom)) {
867 			assertFalse(dst.getObjectDatabase().has(A_txt));
868 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
869 			assertTrue(dst.getObjectDatabase().has(A_txt));
870 			assertEquals(B, dst.exactRef(master).getObjectId());
871 			fsck(dst, B);
872 		}
873 
874 		List<AccessEvent> requests = getRequests();
875 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
876 
877 		AccessEvent info = requests.get(0);
878 		assertEquals("GET", info.getMethod());
879 		assertEquals(join(cloneFrom, "info/refs"), info.getPath());
880 		assertEquals(1, info.getParameters().size());
881 		assertEquals("git-upload-pack", info.getParameter("service"));
882 		assertEquals(200, info.getStatus());
883 		assertEquals("application/x-git-upload-pack-advertisement",
884 				info.getResponseHeader(HDR_CONTENT_TYPE));
885 		if (!enableProtocolV2) {
886 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
887 		}
888 
889 		AccessEvent redirect = requests.get(1);
890 		assertEquals("POST", redirect.getMethod());
891 		assertEquals(301, redirect.getStatus());
892 
893 		for (int i = 2; i < requests.size(); i++) {
894 			AccessEvent service = requests.get(i);
895 			assertEquals("POST", service.getMethod());
896 			assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
897 			assertEquals(0, service.getParameters().size());
898 			assertNotNull("has content-length",
899 					service.getRequestHeader(HDR_CONTENT_LENGTH));
900 			assertNull("not chunked",
901 					service.getRequestHeader(HDR_TRANSFER_ENCODING));
902 			assertEquals(200, service.getStatus());
903 			assertEquals("application/x-git-upload-pack-result",
904 					service.getResponseHeader(HDR_CONTENT_TYPE));
905 		}
906 	}
907 
908 	@Test
909 	public void testInitialClone_RedirectOnPostForbidden() throws Exception {
910 		URIish cloneFrom = extendPath(remoteURI, "/post");
911 		try (Repository dst = createBareRepository();
912 				Transport t = Transport.open(dst, cloneFrom)) {
913 			assertFalse(dst.getObjectDatabase().has(A_txt));
914 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
915 			fail("Should have failed (redirect on POST)");
916 		} catch (TransportException e) {
917 			assertTrue(e.getMessage().contains("301"));
918 		}
919 	}
920 
921 	@Test
922 	public void testInitialClone_RedirectForbidden() throws Exception {
923 		StoredConfig userConfig = SystemReader.getInstance()
924 				.getUserConfig();
925 		userConfig.setString("http", null, "followRedirects", "false");
926 		userConfig.save();
927 
928 		try (Repository dst = createBareRepository();
929 				Transport t = Transport.open(dst, redirectURI)) {
930 			assertFalse(dst.getObjectDatabase().has(A_txt));
931 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
932 			fail("Should have failed (redirects forbidden)");
933 		} catch (TransportException e) {
934 			assertTrue(
935 					e.getMessage().contains("http.followRedirects is false"));
936 		}
937 	}
938 
939 	private void assertFetchRequests(List<AccessEvent> requests, int index) {
940 		AccessEvent info = requests.get(index++);
941 		assertEquals("GET", info.getMethod());
942 		assertEquals(join(authURI, "info/refs"), info.getPath());
943 		assertEquals(1, info.getParameters().size());
944 		assertEquals("git-upload-pack", info.getParameter("service"));
945 		assertEquals(200, info.getStatus());
946 		assertEquals("application/x-git-upload-pack-advertisement",
947 				info.getResponseHeader(HDR_CONTENT_TYPE));
948 		if (!enableProtocolV2) {
949 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
950 		}
951 
952 		for (int i = index; i < requests.size(); i++) {
953 			AccessEvent service = requests.get(i);
954 			assertEquals("POST", service.getMethod());
955 			assertEquals(join(authURI, "git-upload-pack"), service.getPath());
956 			assertEquals(0, service.getParameters().size());
957 			assertNotNull("has content-length",
958 					service.getRequestHeader(HDR_CONTENT_LENGTH));
959 			assertNull("not chunked",
960 					service.getRequestHeader(HDR_TRANSFER_ENCODING));
961 
962 			assertEquals(200, service.getStatus());
963 			assertEquals("application/x-git-upload-pack-result",
964 					service.getResponseHeader(HDR_CONTENT_TYPE));
965 		}
966 	}
967 
968 	@Test
969 	public void testInitialClone_WithAuthentication() throws Exception {
970 		try (Repository dst = createBareRepository();
971 				Transport t = Transport.open(dst, authURI)) {
972 			assertFalse(dst.getObjectDatabase().has(A_txt));
973 			t.setCredentialsProvider(testCredentials);
974 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
975 			assertTrue(dst.getObjectDatabase().has(A_txt));
976 			assertEquals(B, dst.exactRef(master).getObjectId());
977 			fsck(dst, B);
978 		}
979 
980 		List<AccessEvent> requests = getRequests();
981 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
982 
983 		AccessEvent info = requests.get(0);
984 		assertEquals("GET", info.getMethod());
985 		assertEquals(401, info.getStatus());
986 
987 		assertFetchRequests(requests, 1);
988 	}
989 
990 	@Test
991 	public void testInitialClone_WithPreAuthentication() throws Exception {
992 		try (Repository dst = createBareRepository();
993 				Transport t = Transport.open(dst, authURI)) {
994 			assertFalse(dst.getObjectDatabase().has(A_txt));
995 			((TransportHttp) t).setPreemptiveBasicAuthentication(
996 					AppServer.username, AppServer.password);
997 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
998 			assertTrue(dst.getObjectDatabase().has(A_txt));
999 			assertEquals(B, dst.exactRef(master).getObjectId());
1000 			fsck(dst, B);
1001 		}
1002 
1003 		List<AccessEvent> requests = getRequests();
1004 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
1005 
1006 		assertFetchRequests(requests, 0);
1007 	}
1008 
1009 	@Test
1010 	public void testInitialClone_WithPreAuthenticationCleared()
1011 			throws Exception {
1012 		try (Repository dst = createBareRepository();
1013 				Transport t = Transport.open(dst, authURI)) {
1014 			assertFalse(dst.getObjectDatabase().has(A_txt));
1015 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1016 					AppServer.username, AppServer.password);
1017 			((TransportHttp) t).setPreemptiveBasicAuthentication(null, null);
1018 			t.setCredentialsProvider(testCredentials);
1019 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1020 			assertTrue(dst.getObjectDatabase().has(A_txt));
1021 			assertEquals(B, dst.exactRef(master).getObjectId());
1022 			fsck(dst, B);
1023 		}
1024 
1025 		List<AccessEvent> requests = getRequests();
1026 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
1027 
1028 		AccessEvent info = requests.get(0);
1029 		assertEquals("GET", info.getMethod());
1030 		assertEquals(401, info.getStatus());
1031 
1032 		assertFetchRequests(requests, 1);
1033 	}
1034 
1035 	@Test
1036 	public void testInitialClone_PreAuthenticationTooLate() throws Exception {
1037 		try (Repository dst = createBareRepository();
1038 				Transport t = Transport.open(dst, authURI)) {
1039 			assertFalse(dst.getObjectDatabase().has(A_txt));
1040 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1041 					AppServer.username, AppServer.password);
1042 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1043 			assertTrue(dst.getObjectDatabase().has(A_txt));
1044 			assertEquals(B, dst.exactRef(master).getObjectId());
1045 			fsck(dst, B);
1046 			List<AccessEvent> requests = getRequests();
1047 			assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
1048 			assertFetchRequests(requests, 0);
1049 			assertThrows(IllegalStateException.class,
1050 					() -> ((TransportHttp) t).setPreemptiveBasicAuthentication(
1051 							AppServer.username, AppServer.password));
1052 			assertThrows(IllegalStateException.class, () -> ((TransportHttp) t)
1053 					.setPreemptiveBasicAuthentication(null, null));
1054 		}
1055 	}
1056 
1057 	@Test
1058 	public void testInitialClone_WithWrongPreAuthenticationAndCredentialProvider()
1059 			throws Exception {
1060 		try (Repository dst = createBareRepository();
1061 				Transport t = Transport.open(dst, authURI)) {
1062 			assertFalse(dst.getObjectDatabase().has(A_txt));
1063 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1064 					AppServer.username, AppServer.password + 'x');
1065 			t.setCredentialsProvider(testCredentials);
1066 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1067 			assertTrue(dst.getObjectDatabase().has(A_txt));
1068 			assertEquals(B, dst.exactRef(master).getObjectId());
1069 			fsck(dst, B);
1070 		}
1071 
1072 		List<AccessEvent> requests = getRequests();
1073 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
1074 
1075 		AccessEvent info = requests.get(0);
1076 		assertEquals("GET", info.getMethod());
1077 		assertEquals(401, info.getStatus());
1078 
1079 		assertFetchRequests(requests, 1);
1080 	}
1081 
1082 	@Test
1083 	public void testInitialClone_WithWrongPreAuthentication() throws Exception {
1084 		try (Repository dst = createBareRepository();
1085 				Transport t = Transport.open(dst, authURI)) {
1086 			assertFalse(dst.getObjectDatabase().has(A_txt));
1087 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1088 					AppServer.username, AppServer.password + 'x');
1089 			TransportException e = assertThrows(TransportException.class,
1090 					() -> t.fetch(NullProgressMonitor.INSTANCE,
1091 							mirror(master)));
1092 			String msg = e.getMessage();
1093 			assertTrue("Unexpected exception message: " + msg,
1094 					msg.contains("no CredentialsProvider"));
1095 		}
1096 		List<AccessEvent> requests = getRequests();
1097 		assertEquals(1, requests.size());
1098 
1099 		AccessEvent info = requests.get(0);
1100 		assertEquals("GET", info.getMethod());
1101 		assertEquals(401, info.getStatus());
1102 	}
1103 
1104 	@Test
1105 	public void testInitialClone_WithUserInfo() throws Exception {
1106 		URIish withUserInfo = authURI.setUser(AppServer.username)
1107 				.setPass(AppServer.password);
1108 		try (Repository dst = createBareRepository();
1109 				Transport t = Transport.open(dst, withUserInfo)) {
1110 			assertFalse(dst.getObjectDatabase().has(A_txt));
1111 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1112 			assertTrue(dst.getObjectDatabase().has(A_txt));
1113 			assertEquals(B, dst.exactRef(master).getObjectId());
1114 			fsck(dst, B);
1115 		}
1116 
1117 		List<AccessEvent> requests = getRequests();
1118 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
1119 
1120 		assertFetchRequests(requests, 0);
1121 	}
1122 
1123 	@Test
1124 	public void testInitialClone_PreAuthOverridesUserInfo() throws Exception {
1125 		URIish withUserInfo = authURI.setUser(AppServer.username)
1126 				.setPass(AppServer.password + 'x');
1127 		try (Repository dst = createBareRepository();
1128 				Transport t = Transport.open(dst, withUserInfo)) {
1129 			assertFalse(dst.getObjectDatabase().has(A_txt));
1130 			((TransportHttp) t).setPreemptiveBasicAuthentication(
1131 					AppServer.username, AppServer.password);
1132 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1133 			assertTrue(dst.getObjectDatabase().has(A_txt));
1134 			assertEquals(B, dst.exactRef(master).getObjectId());
1135 			fsck(dst, B);
1136 		}
1137 
1138 		List<AccessEvent> requests = getRequests();
1139 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
1140 
1141 		assertFetchRequests(requests, 0);
1142 	}
1143 
1144 	@Test
1145 	public void testInitialClone_WithAuthenticationNoCredentials()
1146 			throws Exception {
1147 		try (Repository dst = createBareRepository();
1148 				Transport t = Transport.open(dst, authURI)) {
1149 			assertFalse(dst.getObjectDatabase().has(A_txt));
1150 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1151 			fail("Should not have succeeded -- no authentication");
1152 		} catch (TransportException e) {
1153 			String msg = e.getMessage();
1154 			assertTrue("Unexpected exception message: " + msg,
1155 					msg.contains("no CredentialsProvider"));
1156 		}
1157 		List<AccessEvent> requests = getRequests();
1158 		assertEquals(1, requests.size());
1159 
1160 		AccessEvent info = requests.get(0);
1161 		assertEquals("GET", info.getMethod());
1162 		assertEquals(401, info.getStatus());
1163 	}
1164 
1165 	@Test
1166 	public void testInitialClone_WithAuthenticationWrongCredentials()
1167 			throws Exception {
1168 		try (Repository dst = createBareRepository();
1169 				Transport t = Transport.open(dst, authURI)) {
1170 			assertFalse(dst.getObjectDatabase().has(A_txt));
1171 			t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
1172 					AppServer.username, "wrongpassword"));
1173 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1174 			fail("Should not have succeeded -- wrong password");
1175 		} catch (TransportException e) {
1176 			String msg = e.getMessage();
1177 			assertTrue("Unexpected exception message: " + msg,
1178 					msg.contains("auth"));
1179 		}
1180 		List<AccessEvent> requests = getRequests();
1181 		// Once without authentication plus three re-tries with authentication
1182 		assertEquals(4, requests.size());
1183 
1184 		for (AccessEvent event : requests) {
1185 			assertEquals("GET", event.getMethod());
1186 			assertEquals(401, event.getStatus());
1187 		}
1188 	}
1189 
1190 	@Test
1191 	public void testInitialClone_WithAuthenticationAfterRedirect()
1192 			throws Exception {
1193 		URIish cloneFrom = extendPath(redirectURI, "/target/auth");
1194 		CredentialsProvider uriSpecificCredentialsProvider = new UsernamePasswordCredentialsProvider(
1195 				"unknown", "none") {
1196 			@Override
1197 			public boolean get(URIish uri, CredentialItem... items)
1198 					throws UnsupportedCredentialItem {
1199 				// Only return the true credentials if the uri path starts with
1200 				// /auth. This ensures that we do provide the correct
1201 				// credentials only for the URi after the redirect, making the
1202 				// test fail if we should be asked for the credentials for the
1203 				// original URI.
1204 				if (uri.getPath().startsWith("/auth")) {
1205 					return testCredentials.get(uri, items);
1206 				}
1207 				return super.get(uri, items);
1208 			}
1209 		};
1210 		try (Repository dst = createBareRepository();
1211 				Transport t = Transport.open(dst, cloneFrom)) {
1212 			assertFalse(dst.getObjectDatabase().has(A_txt));
1213 			t.setCredentialsProvider(uriSpecificCredentialsProvider);
1214 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1215 			assertTrue(dst.getObjectDatabase().has(A_txt));
1216 			assertEquals(B, dst.exactRef(master).getObjectId());
1217 			fsck(dst, B);
1218 		}
1219 
1220 		List<AccessEvent> requests = getRequests();
1221 		assertEquals(enableProtocolV2 ? 5 : 4, requests.size());
1222 
1223 		int requestNumber = 0;
1224 		AccessEvent redirect = requests.get(requestNumber++);
1225 		assertEquals("GET", redirect.getMethod());
1226 		assertEquals(join(cloneFrom, "info/refs"), redirect.getPath());
1227 		assertEquals(301, redirect.getStatus());
1228 
1229 		AccessEvent info = requests.get(requestNumber++);
1230 		assertEquals("GET", info.getMethod());
1231 		assertEquals(join(authURI, "info/refs"), info.getPath());
1232 		assertEquals(401, info.getStatus());
1233 
1234 		info = requests.get(requestNumber++);
1235 		assertEquals("GET", info.getMethod());
1236 		assertEquals(join(authURI, "info/refs"), info.getPath());
1237 		assertEquals(1, info.getParameters().size());
1238 		assertEquals("git-upload-pack", info.getParameter("service"));
1239 		assertEquals(200, info.getStatus());
1240 		assertEquals("application/x-git-upload-pack-advertisement",
1241 				info.getResponseHeader(HDR_CONTENT_TYPE));
1242 		if (!enableProtocolV2) {
1243 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
1244 		} else {
1245 			AccessEvent lsRefs = requests.get(requestNumber++);
1246 			assertEquals("POST", lsRefs.getMethod());
1247 			assertEquals(join(authURI, "git-upload-pack"), lsRefs.getPath());
1248 			assertEquals(0, lsRefs.getParameters().size());
1249 			assertNotNull("has content-length",
1250 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
1251 			assertNull("not chunked",
1252 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
1253 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
1254 			assertEquals(200, lsRefs.getStatus());
1255 			assertEquals("application/x-git-upload-pack-result",
1256 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
1257 		}
1258 
1259 		AccessEvent service = requests.get(requestNumber);
1260 		assertEquals("POST", service.getMethod());
1261 		assertEquals(join(authURI, "git-upload-pack"), service.getPath());
1262 		assertEquals(0, service.getParameters().size());
1263 		assertNotNull("has content-length",
1264 				service.getRequestHeader(HDR_CONTENT_LENGTH));
1265 		assertNull("not chunked",
1266 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
1267 
1268 		assertEquals(200, service.getStatus());
1269 		assertEquals("application/x-git-upload-pack-result",
1270 				service.getResponseHeader(HDR_CONTENT_TYPE));
1271 	}
1272 
1273 	@Test
1274 	public void testInitialClone_WithAuthenticationOnPostOnly()
1275 			throws Exception {
1276 		try (Repository dst = createBareRepository();
1277 				Transport t = Transport.open(dst, authOnPostURI)) {
1278 			assertFalse(dst.getObjectDatabase().has(A_txt));
1279 			t.setCredentialsProvider(testCredentials);
1280 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1281 			assertTrue(dst.getObjectDatabase().has(A_txt));
1282 			assertEquals(B, dst.exactRef(master).getObjectId());
1283 			fsck(dst, B);
1284 		}
1285 
1286 		List<AccessEvent> requests = getRequests();
1287 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
1288 
1289 		AccessEvent info = requests.get(0);
1290 		assertEquals("GET", info.getMethod());
1291 		assertEquals(join(authOnPostURI, "info/refs"), info.getPath());
1292 		assertEquals(1, info.getParameters().size());
1293 		assertEquals("git-upload-pack", info.getParameter("service"));
1294 		assertEquals(200, info.getStatus());
1295 		assertEquals("application/x-git-upload-pack-advertisement",
1296 				info.getResponseHeader(HDR_CONTENT_TYPE));
1297 		if (!enableProtocolV2) {
1298 			assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
1299 		}
1300 
1301 		AccessEvent service = requests.get(1);
1302 		assertEquals("POST", service.getMethod());
1303 		assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath());
1304 		assertEquals(401, service.getStatus());
1305 
1306 		for (int i = 2; i < requests.size(); i++) {
1307 			service = requests.get(i);
1308 			assertEquals("POST", service.getMethod());
1309 			assertEquals(join(authOnPostURI, "git-upload-pack"),
1310 					service.getPath());
1311 			assertEquals(0, service.getParameters().size());
1312 			assertNotNull("has content-length",
1313 					service.getRequestHeader(HDR_CONTENT_LENGTH));
1314 			assertNull("not chunked",
1315 					service.getRequestHeader(HDR_TRANSFER_ENCODING));
1316 
1317 			assertEquals(200, service.getStatus());
1318 			assertEquals("application/x-git-upload-pack-result",
1319 					service.getResponseHeader(HDR_CONTENT_TYPE));
1320 		}
1321 	}
1322 
1323 	@Test
1324 	public void testFetch_FewLocalCommits() throws Exception {
1325 		// Bootstrap by doing the clone.
1326 		//
1327 		TestRepository dst = createTestRepository();
1328 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1329 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1330 		}
1331 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
1332 		List<AccessEvent> cloneRequests = getRequests();
1333 
1334 		// Only create a few new commits.
1335 		TestRepository.BranchBuilder b = dst.branch(master);
1336 		for (int i = 0; i < 4; i++)
1337 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
1338 
1339 		// Create a new commit on the remote.
1340 		//
1341 		RevCommit Z;
1342 		try (TestRepository<Repository> tr = new TestRepository<>(
1343 				remoteRepository)) {
1344 			b = tr.branch(master);
1345 			Z = b.commit().message("Z").create();
1346 		}
1347 
1348 		// Now incrementally update.
1349 		//
1350 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1351 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1352 		}
1353 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
1354 
1355 		List<AccessEvent> requests = getRequests();
1356 		requests.removeAll(cloneRequests);
1357 
1358 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
1359 
1360 		int requestNumber = 0;
1361 		AccessEvent info = requests.get(requestNumber++);
1362 		assertEquals("GET", info.getMethod());
1363 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1364 		assertEquals(1, info.getParameters().size());
1365 		assertEquals("git-upload-pack", info.getParameter("service"));
1366 		assertEquals(200, info.getStatus());
1367 		assertEquals("application/x-git-upload-pack-advertisement",
1368 				info.getResponseHeader(HDR_CONTENT_TYPE));
1369 
1370 		if (enableProtocolV2) {
1371 			AccessEvent lsRefs = requests.get(requestNumber++);
1372 			assertEquals("POST", lsRefs.getMethod());
1373 			assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
1374 			assertEquals(0, lsRefs.getParameters().size());
1375 			assertNotNull("has content-length",
1376 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
1377 			assertNull("not chunked",
1378 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
1379 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
1380 			assertEquals(200, lsRefs.getStatus());
1381 			assertEquals("application/x-git-upload-pack-result",
1382 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
1383 		}
1384 
1385 		// We should have needed one request to perform the fetch.
1386 		//
1387 		AccessEvent service = requests.get(requestNumber);
1388 		assertEquals("POST", service.getMethod());
1389 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
1390 		assertEquals(0, service.getParameters().size());
1391 		assertNotNull("has content-length",
1392 				service.getRequestHeader(HDR_CONTENT_LENGTH));
1393 		assertNull("not chunked",
1394 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
1395 
1396 		assertEquals(200, service.getStatus());
1397 		assertEquals("application/x-git-upload-pack-result",
1398 				service.getResponseHeader(HDR_CONTENT_TYPE));
1399 	}
1400 
1401 	@Test
1402 	public void testFetch_TooManyLocalCommits() throws Exception {
1403 		// Bootstrap by doing the clone.
1404 		//
1405 		TestRepository dst = createTestRepository();
1406 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1407 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1408 		}
1409 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
1410 		List<AccessEvent> cloneRequests = getRequests();
1411 
1412 		// Force enough into the local client that enumeration will
1413 		// need multiple packets, but not too many to overflow and
1414 		// not pick up the ACK_COMMON message.
1415 		//
1416 		TestRepository.BranchBuilder b = dst.branch(master);
1417 		for (int i = 0; i < 32 - 1; i++)
1418 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
1419 
1420 		// Create a new commit on the remote.
1421 		//
1422 		RevCommit Z;
1423 		try (TestRepository<Repository> tr = new TestRepository<>(
1424 				remoteRepository)) {
1425 			b = tr.branch(master);
1426 			Z = b.commit().message("Z").create();
1427 		}
1428 
1429 		// Now incrementally update.
1430 		//
1431 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1432 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1433 		}
1434 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
1435 
1436 		List<AccessEvent> requests = getRequests();
1437 		requests.removeAll(cloneRequests);
1438 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
1439 
1440 		int requestNumber = 0;
1441 		AccessEvent info = requests.get(requestNumber++);
1442 		assertEquals("GET", info.getMethod());
1443 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1444 		assertEquals(1, info.getParameters().size());
1445 		assertEquals("git-upload-pack", info.getParameter("service"));
1446 		assertEquals(200, info.getStatus());
1447 		assertEquals("application/x-git-upload-pack-advertisement", info
1448 				.getResponseHeader(HDR_CONTENT_TYPE));
1449 
1450 		if (enableProtocolV2) {
1451 			AccessEvent lsRefs = requests.get(requestNumber++);
1452 			assertEquals("POST", lsRefs.getMethod());
1453 			assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath());
1454 			assertEquals(0, lsRefs.getParameters().size());
1455 			assertNotNull("has content-length",
1456 					lsRefs.getRequestHeader(HDR_CONTENT_LENGTH));
1457 			assertNull("not chunked",
1458 					lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING));
1459 			assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol"));
1460 			assertEquals(200, lsRefs.getStatus());
1461 			assertEquals("application/x-git-upload-pack-result",
1462 					lsRefs.getResponseHeader(HDR_CONTENT_TYPE));
1463 		}
1464 
1465 		// We should have needed two requests to perform the fetch
1466 		// due to the high number of local unknown commits.
1467 		//
1468 		AccessEvent service = requests.get(requestNumber++);
1469 		assertEquals("POST", service.getMethod());
1470 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
1471 		assertEquals(0, service.getParameters().size());
1472 		assertNotNull("has content-length", service
1473 				.getRequestHeader(HDR_CONTENT_LENGTH));
1474 		assertNull("not chunked", service
1475 				.getRequestHeader(HDR_TRANSFER_ENCODING));
1476 
1477 		assertEquals(200, service.getStatus());
1478 		assertEquals("application/x-git-upload-pack-result", service
1479 				.getResponseHeader(HDR_CONTENT_TYPE));
1480 
1481 		service = requests.get(requestNumber);
1482 		assertEquals("POST", service.getMethod());
1483 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
1484 		assertEquals(0, service.getParameters().size());
1485 		assertNotNull("has content-length", service
1486 				.getRequestHeader(HDR_CONTENT_LENGTH));
1487 		assertNull("not chunked", service
1488 				.getRequestHeader(HDR_TRANSFER_ENCODING));
1489 
1490 		assertEquals(200, service.getStatus());
1491 		assertEquals("application/x-git-upload-pack-result", service
1492 				.getResponseHeader(HDR_CONTENT_TYPE));
1493 	}
1494 
1495 	@Test
1496 	public void testFetch_MaxHavesCutoffAfterAckOnly() throws Exception {
1497 		// Bootstrap by doing the clone.
1498 		//
1499 		TestRepository dst = createTestRepository();
1500 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1501 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1502 		}
1503 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
1504 
1505 		// Force enough into the local client that enumeration will
1506 		// need more than MAX_HAVES (256) haves to be sent. The server
1507 		// doesn't know any of these, so it will never ACK. The client
1508 		// should keep going.
1509 		//
1510 		// If it does, client and server will find a common commit, and
1511 		// the pack file will contain exactly the one commit object Z
1512 		// we create on the remote, which we can test for via the progress
1513 		// monitor, which should have something like
1514 		// "Receiving objects: 100% (1/1)". If the client sends a "done"
1515 		// too early, the server will send more objects, and we'll have
1516 		// a line like "Receiving objects: 100% (8/8)".
1517 		TestRepository.BranchBuilder b = dst.branch(master);
1518 		// The client will send 32 + 64 + 128 + 256 + 512 haves. Only the
1519 		// last one will be a common commit. If the cutoff kicks in too
1520 		// early (after 480), we'll get too many objects in the fetch.
1521 		for (int i = 0; i < 992; i++)
1522 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
1523 
1524 		// Create a new commit on the remote.
1525 		//
1526 		RevCommit Z;
1527 		try (TestRepository<Repository> tr = new TestRepository<>(
1528 				remoteRepository)) {
1529 			b = tr.branch(master);
1530 			Z = b.commit().message("Z").create();
1531 		}
1532 
1533 		// Now incrementally update.
1534 		//
1535 		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1536 		Writer writer = new OutputStreamWriter(buffer, StandardCharsets.UTF_8);
1537 		TextProgressMonitor monitor = new TextProgressMonitor(writer);
1538 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
1539 			t.fetch(monitor, mirror(master));
1540 		}
1541 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
1542 
1543 		String progressMessages = new String(buffer.toByteArray(),
1544 				StandardCharsets.UTF_8);
1545 		Pattern expected = Pattern
1546 				.compile("Receiving objects:\\s+100% \\(1/1\\)\n");
1547 		if (!expected.matcher(progressMessages).find()) {
1548 			System.out.println(progressMessages);
1549 			fail("Expected only one object to be sent");
1550 		}
1551 	}
1552 
1553 	@Test
1554 	public void testInitialClone_BrokenServer() throws Exception {
1555 		try (Repository dst = createBareRepository();
1556 				Transport t = Transport.open(dst, brokenURI)) {
1557 			assertFalse(dst.getObjectDatabase().has(A_txt));
1558 			try {
1559 				t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
1560 				fail("fetch completed despite upload-pack being broken");
1561 			} catch (TransportException err) {
1562 				String exp = brokenURI + ": expected"
1563 						+ " Content-Type application/x-git-upload-pack-result;"
1564 						+ " received Content-Type text/plain;charset=utf-8";
1565 				assertEquals(exp, err.getMessage());
1566 			}
1567 		}
1568 
1569 		List<AccessEvent> requests = getRequests();
1570 		assertEquals(2, requests.size());
1571 
1572 		AccessEvent info = requests.get(0);
1573 		assertEquals("GET", info.getMethod());
1574 		assertEquals(join(brokenURI, "info/refs"), info.getPath());
1575 		assertEquals(1, info.getParameters().size());
1576 		assertEquals("git-upload-pack", info.getParameter("service"));
1577 		assertEquals(200, info.getStatus());
1578 		assertEquals("application/x-git-upload-pack-advertisement", info
1579 				.getResponseHeader(HDR_CONTENT_TYPE));
1580 
1581 		AccessEvent service = requests.get(1);
1582 		assertEquals("POST", service.getMethod());
1583 		assertEquals(join(brokenURI, "git-upload-pack"), service.getPath());
1584 		assertEquals(0, service.getParameters().size());
1585 		assertEquals(200, service.getStatus());
1586 		assertEquals("text/plain;charset=utf-8",
1587 				service.getResponseHeader(HDR_CONTENT_TYPE));
1588 	}
1589 
1590 	@Test
1591 	public void testInvalidWant() throws Exception {
1592 		ObjectId id;
1593 		try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) {
1594 			id = formatter.idFor(Constants.OBJ_BLOB,
1595 					"testInvalidWant".getBytes(UTF_8));
1596 		}
1597 
1598 		try (Repository dst = createBareRepository();
1599 				Transport t = Transport.open(dst, remoteURI);
1600 				FetchConnection c = t.openFetch()) {
1601 			Ref want = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(),
1602 					id);
1603 			c.fetch(NullProgressMonitor.INSTANCE, Collections.singleton(want),
1604 					Collections.<ObjectId> emptySet());
1605 			fail("Server accepted want " + id.name());
1606 		} catch (TransportException err) {
1607 			assertTrue(err.getMessage()
1608 					.contains("want " + id.name() + " not valid"));
1609 		}
1610 	}
1611 
1612 	@Test
1613 	public void testFetch_RefsUnreadableOnUpload() throws Exception {
1614 		AppServer noRefServer = new AppServer();
1615 		try {
1616 			final String repoName = "refs-unreadable";
1617 			RefsUnreadableInMemoryRepository badRefsRepo = new RefsUnreadableInMemoryRepository(
1618 					new DfsRepositoryDescription(repoName));
1619 			final TestRepository<Repository> repo = new TestRepository<>(
1620 					badRefsRepo);
1621 			badRefsRepo.getConfig().setInt("protocol", null, "version",
1622 					enableProtocolV2 ? 2 : 0);
1623 
1624 			ServletContextHandler app = noRefServer.addContext("/git");
1625 			GitServlet gs = new GitServlet();
1626 			gs.setRepositoryResolver(new TestRepositoryResolver(repo, repoName));
1627 			app.addServlet(new ServletHolder(gs), "/*");
1628 			noRefServer.setUp();
1629 
1630 			RevBlob A2_txt = repo.blob("A2");
1631 			RevCommit A2 = repo.commit().add("A2_txt", A2_txt).create();
1632 			RevCommit B2 = repo.commit().parent(A2).add("A2_txt", "C2")
1633 					.add("B2", "B2").create();
1634 			repo.update(master, B2);
1635 
1636 			URIish badRefsURI = new URIish(noRefServer.getURI()
1637 					.resolve(app.getContextPath() + "/" + repoName).toString());
1638 
1639 			try (Repository dst = createBareRepository();
1640 					Transport t = Transport.open(dst, badRefsURI);
1641 					FetchConnection c = t.openFetch()) {
1642 				// We start failing here to exercise the post-advertisement
1643 				// upload pack handler.
1644 				badRefsRepo.startFailing();
1645 				// Need to flush caches because ref advertisement populated them.
1646 				badRefsRepo.getRefDatabase().refresh();
1647 				c.fetch(NullProgressMonitor.INSTANCE,
1648 						Collections.singleton(c.getRef(master)),
1649 						Collections.<ObjectId> emptySet());
1650 				fail("Successfully served ref with value " + c.getRef(master));
1651 			} catch (TransportException err) {
1652 				assertTrue("Unexpected exception message " + err.getMessage(),
1653 						err.getMessage().contains("Internal server error"));
1654 			}
1655 		} finally {
1656 			noRefServer.tearDown();
1657 		}
1658 	}
1659 
1660 	@Test
1661 	public void testPush_NotAuthorized() throws Exception {
1662 		final TestRepository src = createTestRepository();
1663 		final RevBlob Q_txt = src.blob("new text");
1664 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
1665 		final Repository db = src.getRepository();
1666 		final String dstName = Constants.R_HEADS + "new.branch";
1667 
1668 		// push anonymous shouldn't be allowed.
1669 		//
1670 		try (Transport t = Transport.open(db, remoteURI)) {
1671 			final String srcExpr = Q.name();
1672 			final boolean forceUpdate = false;
1673 			final String localName = null;
1674 			final ObjectId oldId = null;
1675 
1676 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
1677 					srcExpr, dstName, forceUpdate, localName, oldId);
1678 			try {
1679 				t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
1680 				fail("anonymous push incorrectly accepted without error");
1681 			} catch (TransportException e) {
1682 				final String exp = remoteURI + ": "
1683 						+ JGitText.get().authenticationNotSupported;
1684 				assertEquals(exp, e.getMessage());
1685 			}
1686 		}
1687 
1688 		List<AccessEvent> requests = getRequests();
1689 		assertEquals(1, requests.size());
1690 
1691 		AccessEvent info = requests.get(0);
1692 		assertEquals("GET", info.getMethod());
1693 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1694 		assertEquals(1, info.getParameters().size());
1695 		assertEquals("git-receive-pack", info.getParameter("service"));
1696 		assertEquals(401, info.getStatus());
1697 	}
1698 
1699 	@Test
1700 	public void testPush_CreateBranch() throws Exception {
1701 		final TestRepository src = createTestRepository();
1702 		final RevBlob Q_txt = src.blob("new text");
1703 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
1704 		final Repository db = src.getRepository();
1705 		final String dstName = Constants.R_HEADS + "new.branch";
1706 
1707 		enableReceivePack();
1708 
1709 		try (Transport t = Transport.open(db, remoteURI)) {
1710 			final String srcExpr = Q.name();
1711 			final boolean forceUpdate = false;
1712 			final String localName = null;
1713 			final ObjectId oldId = null;
1714 
1715 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
1716 					srcExpr, dstName, forceUpdate, localName, oldId);
1717 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
1718 		}
1719 
1720 		assertTrue(remoteRepository.getObjectDatabase().has(Q_txt));
1721 		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
1722 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
1723 		fsck(remoteRepository, Q);
1724 
1725 		final ReflogReader log = remoteRepository.getReflogReader(dstName);
1726 		assertNotNull("has log for " + dstName, log);
1727 
1728 		final ReflogEntry last = log.getLastEntry();
1729 		assertNotNull("has last entry", last);
1730 		assertEquals(ObjectId.zeroId(), last.getOldId());
1731 		assertEquals(Q, last.getNewId());
1732 		assertEquals("anonymous", last.getWho().getName());
1733 
1734 		// Assumption: The host name we use to contact the server should
1735 		// be the server's own host name, because it should be the loopback
1736 		// network interface.
1737 		//
1738 		final String clientHost = remoteURI.getHost();
1739 		assertEquals("anonymous@" + clientHost, last.getWho().getEmailAddress());
1740 		assertEquals("push: created", last.getComment());
1741 
1742 		List<AccessEvent> requests = getRequests();
1743 		assertEquals(2, requests.size());
1744 
1745 		AccessEvent info = requests.get(0);
1746 		assertEquals("GET", info.getMethod());
1747 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1748 		assertEquals(1, info.getParameters().size());
1749 		assertEquals("git-receive-pack", info.getParameter("service"));
1750 		assertEquals(200, info.getStatus());
1751 		assertEquals("application/x-git-receive-pack-advertisement", info
1752 				.getResponseHeader(HDR_CONTENT_TYPE));
1753 
1754 		AccessEvent service = requests.get(1);
1755 		assertEquals("POST", service.getMethod());
1756 		assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
1757 		assertEquals(0, service.getParameters().size());
1758 		assertNotNull("has content-length", service
1759 				.getRequestHeader(HDR_CONTENT_LENGTH));
1760 		assertNull("not chunked", service
1761 				.getRequestHeader(HDR_TRANSFER_ENCODING));
1762 
1763 		assertEquals(200, service.getStatus());
1764 		assertEquals("application/x-git-receive-pack-result", service
1765 				.getResponseHeader(HDR_CONTENT_TYPE));
1766 	}
1767 
1768 	@Test
1769 	public void testPush_ChunkedEncoding() throws Exception {
1770 		final TestRepository<Repository> src = createTestRepository();
1771 		final RevBlob Q_bin = src.blob(new TestRng("Q").nextBytes(128 * 1024));
1772 		final RevCommit Q = src.commit().add("Q", Q_bin).create();
1773 		final Repository db = src.getRepository();
1774 		final String dstName = Constants.R_HEADS + "new.branch";
1775 
1776 		enableReceivePack();
1777 
1778 		final StoredConfig cfg = db.getConfig();
1779 		cfg.setInt("core", null, "compression", 0);
1780 		cfg.setInt("http", null, "postbuffer", 8 * 1024);
1781 		cfg.save();
1782 
1783 		try (Transport t = Transport.open(db, remoteURI)) {
1784 			final String srcExpr = Q.name();
1785 			final boolean forceUpdate = false;
1786 			final String localName = null;
1787 			final ObjectId oldId = null;
1788 
1789 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
1790 					srcExpr, dstName, forceUpdate, localName, oldId);
1791 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
1792 		}
1793 
1794 		assertTrue(remoteRepository.getObjectDatabase().has(Q_bin));
1795 		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
1796 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
1797 		fsck(remoteRepository, Q);
1798 
1799 		List<AccessEvent> requests = getRequests();
1800 		assertEquals(2, requests.size());
1801 
1802 		AccessEvent info = requests.get(0);
1803 		assertEquals("GET", info.getMethod());
1804 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
1805 		assertEquals(1, info.getParameters().size());
1806 		assertEquals("git-receive-pack", info.getParameter("service"));
1807 		assertEquals(200, info.getStatus());
1808 		assertEquals("application/x-git-receive-pack-advertisement", info
1809 				.getResponseHeader(HDR_CONTENT_TYPE));
1810 
1811 		AccessEvent service = requests.get(1);
1812 		assertEquals("POST", service.getMethod());
1813 		assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
1814 		assertEquals(0, service.getParameters().size());
1815 		assertNull("no content-length", service
1816 				.getRequestHeader(HDR_CONTENT_LENGTH));
1817 		assertEquals("chunked", service.getRequestHeader(HDR_TRANSFER_ENCODING));
1818 
1819 		assertEquals(200, service.getStatus());
1820 		assertEquals("application/x-git-receive-pack-result", service
1821 				.getResponseHeader(HDR_CONTENT_TYPE));
1822 	}
1823 
1824 	private void enableReceivePack() throws IOException {
1825 		final StoredConfig cfg = remoteRepository.getConfig();
1826 		cfg.setBoolean("http", null, "receivepack", true);
1827 		cfg.save();
1828 	}
1829 }