View Javadoc
1   /*
2    * Copyright (C) 2010, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.http.test;
45  
46  import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
47  import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH;
48  import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
49  import static org.junit.Assert.assertEquals;
50  import static org.junit.Assert.assertFalse;
51  import static org.junit.Assert.assertNotNull;
52  import static org.junit.Assert.assertNull;
53  import static org.junit.Assert.assertTrue;
54  import static org.junit.Assert.fail;
55  
56  import java.io.IOException;
57  import java.io.PrintWriter;
58  import java.net.URISyntaxException;
59  import java.nio.charset.StandardCharsets;
60  import java.util.Arrays;
61  import java.util.Collection;
62  import java.util.Collections;
63  import java.util.EnumSet;
64  import java.util.List;
65  import java.util.Map;
66  
67  import javax.servlet.DispatcherType;
68  import javax.servlet.Filter;
69  import javax.servlet.FilterChain;
70  import javax.servlet.FilterConfig;
71  import javax.servlet.ServletException;
72  import javax.servlet.ServletRequest;
73  import javax.servlet.ServletResponse;
74  import javax.servlet.http.HttpServletRequest;
75  import javax.servlet.http.HttpServletResponse;
76  
77  import org.eclipse.jetty.servlet.FilterHolder;
78  import org.eclipse.jetty.servlet.ServletContextHandler;
79  import org.eclipse.jetty.servlet.ServletHolder;
80  import org.eclipse.jgit.errors.RemoteRepositoryException;
81  import org.eclipse.jgit.errors.RepositoryNotFoundException;
82  import org.eclipse.jgit.errors.TransportException;
83  import org.eclipse.jgit.http.server.GitServlet;
84  import org.eclipse.jgit.internal.JGitText;
85  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
86  import org.eclipse.jgit.junit.TestRepository;
87  import org.eclipse.jgit.junit.TestRng;
88  import org.eclipse.jgit.junit.http.AccessEvent;
89  import org.eclipse.jgit.junit.http.AppServer;
90  import org.eclipse.jgit.junit.http.HttpTestCase;
91  import org.eclipse.jgit.lib.ConfigConstants;
92  import org.eclipse.jgit.lib.Constants;
93  import org.eclipse.jgit.lib.NullProgressMonitor;
94  import org.eclipse.jgit.lib.ObjectId;
95  import org.eclipse.jgit.lib.ObjectIdRef;
96  import org.eclipse.jgit.lib.ObjectInserter;
97  import org.eclipse.jgit.lib.Ref;
98  import org.eclipse.jgit.lib.ReflogEntry;
99  import org.eclipse.jgit.lib.ReflogReader;
100 import org.eclipse.jgit.lib.Repository;
101 import org.eclipse.jgit.lib.StoredConfig;
102 import org.eclipse.jgit.revwalk.RevBlob;
103 import org.eclipse.jgit.revwalk.RevCommit;
104 import org.eclipse.jgit.transport.FetchConnection;
105 import org.eclipse.jgit.transport.HttpTransport;
106 import org.eclipse.jgit.transport.RemoteRefUpdate;
107 import org.eclipse.jgit.transport.Transport;
108 import org.eclipse.jgit.transport.TransportHttp;
109 import org.eclipse.jgit.transport.URIish;
110 import org.eclipse.jgit.transport.http.HttpConnectionFactory;
111 import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
112 import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
113 import org.eclipse.jgit.transport.resolver.RepositoryResolver;
114 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
115 import org.junit.Before;
116 import org.junit.Test;
117 import org.junit.runner.RunWith;
118 import org.junit.runners.Parameterized;
119 import org.junit.runners.Parameterized.Parameters;
120 
121 @RunWith(Parameterized.class)
122 public class SmartClientSmartServerTest extends HttpTestCase {
123 	private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding";
124 
125 	private Repository remoteRepository;
126 
127 	private URIish remoteURI;
128 
129 	private URIish brokenURI;
130 
131 	private RevBlob A_txt;
132 
133 	private RevCommit A, B;
134 
135 	@Parameters
136 	public static Collection<Object[]> data() {
137 		// run all tests with both connection factories we have
138 		return Arrays.asList(new Object[][] {
139 				{ new JDKHttpConnectionFactory() },
140 				{ new HttpClientConnectionFactory() } });
141 	}
142 
143 	public SmartClientSmartServerTest(HttpConnectionFactory cf) {
144 		HttpTransport.setConnectionFactory(cf);
145 	}
146 
147 	@Before
148 	public void setUp() throws Exception {
149 		super.setUp();
150 
151 		final TestRepository<Repository> src = createTestRepository();
152 		final String srcName = src.getRepository().getDirectory().getName();
153 		src.getRepository()
154 				.getConfig()
155 				.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
156 						ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
157 
158 		ServletContextHandler app = server.addContext("/git");
159 		GitServlet gs = new GitServlet();
160 		gs.setRepositoryResolver(new TestRepoResolver(src, srcName));
161 		app.addServlet(new ServletHolder(gs), "/*");
162 
163 		ServletContextHandler broken = server.addContext("/bad");
164 		broken.addFilter(new FilterHolder(new Filter() {
165 			public void doFilter(ServletRequest request,
166 					ServletResponse response, FilterChain chain)
167 					throws IOException, ServletException {
168 				final HttpServletResponse r = (HttpServletResponse) response;
169 				r.setContentType("text/plain");
170 				r.setCharacterEncoding("UTF-8");
171 				PrintWriter w = r.getWriter();
172 				w.print("OK");
173 				w.close();
174 			}
175 
176 			public void init(FilterConfig filterConfig) throws ServletException {
177 				//
178 			}
179 
180 			public void destroy() {
181 				//
182 			}
183 		}), "/" + srcName + "/git-upload-pack",
184 				EnumSet.of(DispatcherType.REQUEST));
185 		broken.addServlet(new ServletHolder(gs), "/*");
186 
187 		server.setUp();
188 
189 		remoteRepository = src.getRepository();
190 		remoteURI = toURIish(app, srcName);
191 		brokenURI = toURIish(broken, srcName);
192 
193 		A_txt = src.blob("A");
194 		A = src.commit().add("A_txt", A_txt).create();
195 		B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create();
196 		src.update(master, B);
197 
198 		src.update("refs/garbage/a/very/long/ref/name/to/compress", B);
199 	}
200 
201 	@Test
202 	public void testListRemote() throws IOException {
203 		Repository dst = createBareRepository();
204 
205 		assertEquals("http", remoteURI.getScheme());
206 
207 		Map<String, Ref> map;
208 		try (Transport t = Transport.open(dst, remoteURI)) {
209 			// I didn't make up these public interface names, I just
210 			// approved them for inclusion into the code base. Sorry.
211 			// --spearce
212 			//
213 			assertTrue("isa TransportHttp", t instanceof TransportHttp);
214 			assertTrue("isa HttpTransport", t instanceof HttpTransport);
215 
216 			FetchConnection c = t.openFetch();
217 			try {
218 				map = c.getRefsMap();
219 			} finally {
220 				c.close();
221 			}
222 		}
223 
224 		assertNotNull("have map of refs", map);
225 		assertEquals(3, map.size());
226 
227 		assertNotNull("has " + master, map.get(master));
228 		assertEquals(B, map.get(master).getObjectId());
229 
230 		assertNotNull("has " + Constants.HEAD, map.get(Constants.HEAD));
231 		assertEquals(B, map.get(Constants.HEAD).getObjectId());
232 
233 		List<AccessEvent> requests = getRequests();
234 		assertEquals(1, requests.size());
235 
236 		AccessEvent info = requests.get(0);
237 		assertEquals("GET", info.getMethod());
238 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
239 		assertEquals(1, info.getParameters().size());
240 		assertEquals("git-upload-pack", info.getParameter("service"));
241 		assertEquals(200, info.getStatus());
242 		assertEquals("application/x-git-upload-pack-advertisement", info
243 				.getResponseHeader(HDR_CONTENT_TYPE));
244 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
245 	}
246 
247 	@Test
248 	public void testListRemote_BadName() throws IOException, URISyntaxException {
249 		Repository dst = createBareRepository();
250 		URIish uri = new URIish(this.remoteURI.toString() + ".invalid");
251 		try (Transport t = Transport.open(dst, uri)) {
252 			try {
253 				t.openFetch();
254 				fail("fetch connection opened");
255 			} catch (RemoteRepositoryException notFound) {
256 				assertEquals(uri + ": Git repository not found",
257 						notFound.getMessage());
258 			}
259 		}
260 
261 		List<AccessEvent> requests = getRequests();
262 		assertEquals(1, requests.size());
263 
264 		AccessEvent info = requests.get(0);
265 		assertEquals("GET", info.getMethod());
266 		assertEquals(join(uri, "info/refs"), info.getPath());
267 		assertEquals(1, info.getParameters().size());
268 		assertEquals("git-upload-pack", info.getParameter("service"));
269 		assertEquals(200, info.getStatus());
270 		assertEquals("application/x-git-upload-pack-advertisement",
271 				info.getResponseHeader(HDR_CONTENT_TYPE));
272 	}
273 
274 	@Test
275 	public void testInitialClone_Small() throws Exception {
276 		Repository dst = createBareRepository();
277 		assertFalse(dst.hasObject(A_txt));
278 
279 		try (Transport t = Transport.open(dst, remoteURI)) {
280 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
281 		}
282 
283 		assertTrue(dst.hasObject(A_txt));
284 		assertEquals(B, dst.exactRef(master).getObjectId());
285 		fsck(dst, B);
286 
287 		List<AccessEvent> requests = getRequests();
288 		assertEquals(2, requests.size());
289 
290 		AccessEvent info = requests.get(0);
291 		assertEquals("GET", info.getMethod());
292 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
293 		assertEquals(1, info.getParameters().size());
294 		assertEquals("git-upload-pack", info.getParameter("service"));
295 		assertEquals(200, info.getStatus());
296 		assertEquals("application/x-git-upload-pack-advertisement", info
297 				.getResponseHeader(HDR_CONTENT_TYPE));
298 		assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
299 
300 		AccessEvent service = requests.get(1);
301 		assertEquals("POST", service.getMethod());
302 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
303 		assertEquals(0, service.getParameters().size());
304 		assertNotNull("has content-length", service
305 				.getRequestHeader(HDR_CONTENT_LENGTH));
306 		assertNull("not chunked", service
307 				.getRequestHeader(HDR_TRANSFER_ENCODING));
308 
309 		assertEquals(200, service.getStatus());
310 		assertEquals("application/x-git-upload-pack-result", service
311 				.getResponseHeader(HDR_CONTENT_TYPE));
312 	}
313 
314 	@Test
315 	public void testFetch_FewLocalCommits() throws Exception {
316 		// Bootstrap by doing the clone.
317 		//
318 		TestRepository dst = createTestRepository();
319 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
320 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
321 		}
322 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
323 		List<AccessEvent> cloneRequests = getRequests();
324 
325 		// Only create a few new commits.
326 		TestRepository.BranchBuilder b = dst.branch(master);
327 		for (int i = 0; i < 4; i++)
328 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
329 
330 		// Create a new commit on the remote.
331 		//
332 		b = new TestRepository<Repository>(remoteRepository).branch(master);
333 		RevCommit Z = b.commit().message("Z").create();
334 
335 		// Now incrementally update.
336 		//
337 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
338 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
339 		}
340 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
341 
342 		List<AccessEvent> requests = getRequests();
343 		requests.removeAll(cloneRequests);
344 		assertEquals(2, requests.size());
345 
346 		AccessEvent info = requests.get(0);
347 		assertEquals("GET", info.getMethod());
348 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
349 		assertEquals(1, info.getParameters().size());
350 		assertEquals("git-upload-pack", info.getParameter("service"));
351 		assertEquals(200, info.getStatus());
352 		assertEquals("application/x-git-upload-pack-advertisement",
353 				info.getResponseHeader(HDR_CONTENT_TYPE));
354 
355 		// We should have needed one request to perform the fetch.
356 		//
357 		AccessEvent service = requests.get(1);
358 		assertEquals("POST", service.getMethod());
359 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
360 		assertEquals(0, service.getParameters().size());
361 		assertNotNull("has content-length",
362 				service.getRequestHeader(HDR_CONTENT_LENGTH));
363 		assertNull("not chunked",
364 				service.getRequestHeader(HDR_TRANSFER_ENCODING));
365 
366 		assertEquals(200, service.getStatus());
367 		assertEquals("application/x-git-upload-pack-result",
368 				service.getResponseHeader(HDR_CONTENT_TYPE));
369 	}
370 
371 	@Test
372 	public void testFetch_TooManyLocalCommits() throws Exception {
373 		// Bootstrap by doing the clone.
374 		//
375 		TestRepository dst = createTestRepository();
376 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
377 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
378 		}
379 		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
380 		List<AccessEvent> cloneRequests = getRequests();
381 
382 		// Force enough into the local client that enumeration will
383 		// need multiple packets, but not too many to overflow and
384 		// not pick up the ACK_COMMON message.
385 		//
386 		TestRepository.BranchBuilder b = dst.branch(master);
387 		for (int i = 0; i < 32 - 1; i++)
388 			b.commit().tick(3600 /* 1 hour */).message("c" + i).create();
389 
390 		// Create a new commit on the remote.
391 		//
392 		b = new TestRepository<Repository>(remoteRepository).branch(master);
393 		RevCommit Z = b.commit().message("Z").create();
394 
395 		// Now incrementally update.
396 		//
397 		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
398 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
399 		}
400 		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
401 
402 		List<AccessEvent> requests = getRequests();
403 		requests.removeAll(cloneRequests);
404 		assertEquals(3, requests.size());
405 
406 		AccessEvent info = requests.get(0);
407 		assertEquals("GET", info.getMethod());
408 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
409 		assertEquals(1, info.getParameters().size());
410 		assertEquals("git-upload-pack", info.getParameter("service"));
411 		assertEquals(200, info.getStatus());
412 		assertEquals("application/x-git-upload-pack-advertisement", info
413 				.getResponseHeader(HDR_CONTENT_TYPE));
414 
415 		// We should have needed two requests to perform the fetch
416 		// due to the high number of local unknown commits.
417 		//
418 		AccessEvent service = requests.get(1);
419 		assertEquals("POST", service.getMethod());
420 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
421 		assertEquals(0, service.getParameters().size());
422 		assertNotNull("has content-length", service
423 				.getRequestHeader(HDR_CONTENT_LENGTH));
424 		assertNull("not chunked", service
425 				.getRequestHeader(HDR_TRANSFER_ENCODING));
426 
427 		assertEquals(200, service.getStatus());
428 		assertEquals("application/x-git-upload-pack-result", service
429 				.getResponseHeader(HDR_CONTENT_TYPE));
430 
431 		service = requests.get(2);
432 		assertEquals("POST", service.getMethod());
433 		assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
434 		assertEquals(0, service.getParameters().size());
435 		assertNotNull("has content-length", service
436 				.getRequestHeader(HDR_CONTENT_LENGTH));
437 		assertNull("not chunked", service
438 				.getRequestHeader(HDR_TRANSFER_ENCODING));
439 
440 		assertEquals(200, service.getStatus());
441 		assertEquals("application/x-git-upload-pack-result", service
442 				.getResponseHeader(HDR_CONTENT_TYPE));
443 	}
444 
445 	@Test
446 	public void testInitialClone_BrokenServer() throws Exception {
447 		Repository dst = createBareRepository();
448 		assertFalse(dst.hasObject(A_txt));
449 
450 		try (Transport t = Transport.open(dst, brokenURI)) {
451 			try {
452 				t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
453 				fail("fetch completed despite upload-pack being broken");
454 			} catch (TransportException err) {
455 				String exp = brokenURI + ": expected"
456 						+ " Content-Type application/x-git-upload-pack-result;"
457 						+ " received Content-Type text/plain; charset=UTF-8";
458 				assertEquals(exp, err.getMessage());
459 			}
460 		}
461 
462 		List<AccessEvent> requests = getRequests();
463 		assertEquals(2, requests.size());
464 
465 		AccessEvent info = requests.get(0);
466 		assertEquals("GET", info.getMethod());
467 		assertEquals(join(brokenURI, "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 
474 		AccessEvent service = requests.get(1);
475 		assertEquals("POST", service.getMethod());
476 		assertEquals(join(brokenURI, "git-upload-pack"), service.getPath());
477 		assertEquals(0, service.getParameters().size());
478 		assertEquals(200, service.getStatus());
479 		assertEquals("text/plain; charset=UTF-8",
480 				service.getResponseHeader(HDR_CONTENT_TYPE));
481 	}
482 
483 	@Test
484 	public void testInvalidWant() throws Exception {
485 		@SuppressWarnings("resource")
486 		ObjectId id = new ObjectInserter.Formatter().idFor(Constants.OBJ_BLOB,
487 				"testInvalidWant".getBytes(StandardCharsets.UTF_8));
488 
489 		Repository dst = createBareRepository();
490 		try (Transport t = Transport.open(dst, remoteURI);
491 				FetchConnection c = t.openFetch()) {
492 			Ref want = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(),
493 					id);
494 			c.fetch(NullProgressMonitor.INSTANCE, Collections.singleton(want),
495 					Collections.<ObjectId> emptySet());
496 			fail("Server accepted want " + id.name());
497 		} catch (TransportException err) {
498 			assertEquals("want " + id.name() + " not valid", err.getMessage());
499 		}
500 	}
501 
502 	@Test
503 	public void testFetch_RefsUnreadableOnUpload() throws Exception {
504 		AppServer noRefServer = new AppServer();
505 		try {
506 			final String repoName = "refs-unreadable";
507 			RefsUnreadableInMemoryRepository badRefsRepo = new RefsUnreadableInMemoryRepository(
508 					new DfsRepositoryDescription(repoName));
509 			final TestRepository<Repository> repo = new TestRepository<Repository>(
510 					badRefsRepo);
511 
512 			ServletContextHandler app = noRefServer.addContext("/git");
513 			GitServlet gs = new GitServlet();
514 			gs.setRepositoryResolver(new TestRepoResolver(repo, repoName));
515 			app.addServlet(new ServletHolder(gs), "/*");
516 			noRefServer.setUp();
517 
518 			RevBlob A2_txt = repo.blob("A2");
519 			RevCommit A2 = repo.commit().add("A2_txt", A2_txt).create();
520 			RevCommit B2 = repo.commit().parent(A2).add("A2_txt", "C2")
521 					.add("B2", "B2").create();
522 			repo.update(master, B2);
523 
524 			URIish badRefsURI = new URIish(noRefServer.getURI()
525 					.resolve(app.getContextPath() + "/" + repoName).toString());
526 
527 			Repository dst = createBareRepository();
528 			try (Transport t = Transport.open(dst, badRefsURI);
529 					FetchConnection c = t.openFetch()) {
530 				// We start failing here to exercise the post-advertisement
531 				// upload pack handler.
532 				badRefsRepo.startFailing();
533 				// Need to flush caches because ref advertisement populated them.
534 				badRefsRepo.getRefDatabase().refresh();
535 				c.fetch(NullProgressMonitor.INSTANCE,
536 						Collections.singleton(c.getRef(master)),
537 						Collections.<ObjectId> emptySet());
538 				fail("Successfully served ref with value " + c.getRef(master));
539 			} catch (TransportException err) {
540 				assertEquals("internal server error", err.getMessage());
541 			}
542 		} finally {
543 			noRefServer.tearDown();
544 		}
545 	}
546 
547 	@Test
548 	public void testPush_NotAuthorized() throws Exception {
549 		final TestRepository src = createTestRepository();
550 		final RevBlob Q_txt = src.blob("new text");
551 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
552 		final Repository db = src.getRepository();
553 		final String dstName = Constants.R_HEADS + "new.branch";
554 
555 		// push anonymous shouldn't be allowed.
556 		//
557 		try (Transport t = Transport.open(db, remoteURI)) {
558 			final String srcExpr = Q.name();
559 			final boolean forceUpdate = false;
560 			final String localName = null;
561 			final ObjectId oldId = null;
562 
563 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
564 					srcExpr, dstName, forceUpdate, localName, oldId);
565 			try {
566 				t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
567 				fail("anonymous push incorrectly accepted without error");
568 			} catch (TransportException e) {
569 				final String exp = remoteURI + ": "
570 						+ JGitText.get().authenticationNotSupported;
571 				assertEquals(exp, e.getMessage());
572 			}
573 		}
574 
575 		List<AccessEvent> requests = getRequests();
576 		assertEquals(1, requests.size());
577 
578 		AccessEvent info = requests.get(0);
579 		assertEquals("GET", info.getMethod());
580 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
581 		assertEquals(1, info.getParameters().size());
582 		assertEquals("git-receive-pack", info.getParameter("service"));
583 		assertEquals(401, info.getStatus());
584 	}
585 
586 	@Test
587 	public void testPush_CreateBranch() throws Exception {
588 		final TestRepository src = createTestRepository();
589 		final RevBlob Q_txt = src.blob("new text");
590 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
591 		final Repository db = src.getRepository();
592 		final String dstName = Constants.R_HEADS + "new.branch";
593 
594 		enableReceivePack();
595 
596 		try (Transport t = Transport.open(db, remoteURI)) {
597 			final String srcExpr = Q.name();
598 			final boolean forceUpdate = false;
599 			final String localName = null;
600 			final ObjectId oldId = null;
601 
602 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
603 					srcExpr, dstName, forceUpdate, localName, oldId);
604 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
605 		}
606 
607 		assertTrue(remoteRepository.hasObject(Q_txt));
608 		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
609 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
610 		fsck(remoteRepository, Q);
611 
612 		final ReflogReader log = remoteRepository.getReflogReader(dstName);
613 		assertNotNull("has log for " + dstName, log);
614 
615 		final ReflogEntry last = log.getLastEntry();
616 		assertNotNull("has last entry", last);
617 		assertEquals(ObjectId.zeroId(), last.getOldId());
618 		assertEquals(Q, last.getNewId());
619 		assertEquals("anonymous", last.getWho().getName());
620 
621 		// Assumption: The host name we use to contact the server should
622 		// be the server's own host name, because it should be the loopback
623 		// network interface.
624 		//
625 		final String clientHost = remoteURI.getHost();
626 		assertEquals("anonymous@" + clientHost, last.getWho().getEmailAddress());
627 		assertEquals("push: created", last.getComment());
628 
629 		List<AccessEvent> requests = getRequests();
630 		assertEquals(2, requests.size());
631 
632 		AccessEvent info = requests.get(0);
633 		assertEquals("GET", info.getMethod());
634 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
635 		assertEquals(1, info.getParameters().size());
636 		assertEquals("git-receive-pack", info.getParameter("service"));
637 		assertEquals(200, info.getStatus());
638 		assertEquals("application/x-git-receive-pack-advertisement", info
639 				.getResponseHeader(HDR_CONTENT_TYPE));
640 
641 		AccessEvent service = requests.get(1);
642 		assertEquals("POST", service.getMethod());
643 		assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
644 		assertEquals(0, service.getParameters().size());
645 		assertNotNull("has content-length", service
646 				.getRequestHeader(HDR_CONTENT_LENGTH));
647 		assertNull("not chunked", service
648 				.getRequestHeader(HDR_TRANSFER_ENCODING));
649 
650 		assertEquals(200, service.getStatus());
651 		assertEquals("application/x-git-receive-pack-result", service
652 				.getResponseHeader(HDR_CONTENT_TYPE));
653 	}
654 
655 	@Test
656 	public void testPush_ChunkedEncoding() throws Exception {
657 		final TestRepository<Repository> src = createTestRepository();
658 		final RevBlob Q_bin = src.blob(new TestRng("Q").nextBytes(128 * 1024));
659 		final RevCommit Q = src.commit().add("Q", Q_bin).create();
660 		final Repository db = src.getRepository();
661 		final String dstName = Constants.R_HEADS + "new.branch";
662 
663 		enableReceivePack();
664 
665 		final StoredConfig cfg = db.getConfig();
666 		cfg.setInt("core", null, "compression", 0);
667 		cfg.setInt("http", null, "postbuffer", 8 * 1024);
668 		cfg.save();
669 
670 		try (Transport t = Transport.open(db, remoteURI)) {
671 			final String srcExpr = Q.name();
672 			final boolean forceUpdate = false;
673 			final String localName = null;
674 			final ObjectId oldId = null;
675 
676 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
677 					srcExpr, dstName, forceUpdate, localName, oldId);
678 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
679 		}
680 
681 		assertTrue(remoteRepository.hasObject(Q_bin));
682 		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
683 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
684 		fsck(remoteRepository, Q);
685 
686 		List<AccessEvent> requests = getRequests();
687 		assertEquals(2, requests.size());
688 
689 		AccessEvent info = requests.get(0);
690 		assertEquals("GET", info.getMethod());
691 		assertEquals(join(remoteURI, "info/refs"), info.getPath());
692 		assertEquals(1, info.getParameters().size());
693 		assertEquals("git-receive-pack", info.getParameter("service"));
694 		assertEquals(200, info.getStatus());
695 		assertEquals("application/x-git-receive-pack-advertisement", info
696 				.getResponseHeader(HDR_CONTENT_TYPE));
697 
698 		AccessEvent service = requests.get(1);
699 		assertEquals("POST", service.getMethod());
700 		assertEquals(join(remoteURI, "git-receive-pack"), service.getPath());
701 		assertEquals(0, service.getParameters().size());
702 		assertNull("no content-length", service
703 				.getRequestHeader(HDR_CONTENT_LENGTH));
704 		assertEquals("chunked", service.getRequestHeader(HDR_TRANSFER_ENCODING));
705 
706 		assertEquals(200, service.getStatus());
707 		assertEquals("application/x-git-receive-pack-result", service
708 				.getResponseHeader(HDR_CONTENT_TYPE));
709 	}
710 
711 	private void enableReceivePack() throws IOException {
712 		final StoredConfig cfg = remoteRepository.getConfig();
713 		cfg.setBoolean("http", null, "receivepack", true);
714 		cfg.save();
715 	}
716 
717 	private final class TestRepoResolver
718 			implements RepositoryResolver<HttpServletRequest> {
719 
720 		private final TestRepository<Repository> repo;
721 
722 		private final String repoName;
723 
724 		private TestRepoResolver(TestRepository<Repository> repo,
725 				String repoName) {
726 			this.repo = repo;
727 			this.repoName = repoName;
728 		}
729 
730 		public Repository open(HttpServletRequest req, String name)
731 				throws RepositoryNotFoundException, ServiceNotEnabledException {
732 			if (!name.equals(repoName))
733 				throw new RepositoryNotFoundException(name);
734 
735 			Repository db = repo.getRepository();
736 			db.incrementOpen();
737 			return db;
738 		}
739 	}
740 }