View Javadoc
1   /*
2    * Copyright (C) 2015, 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.transport;
12  
13  import static org.junit.Assert.assertEquals;
14  import static org.junit.Assert.assertNull;
15  import static org.junit.Assert.assertTrue;
16  import static org.junit.Assert.fail;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  import java.util.concurrent.atomic.AtomicInteger;
21  
22  import org.eclipse.jgit.api.Git;
23  import org.eclipse.jgit.api.errors.InvalidRemoteException;
24  import org.eclipse.jgit.api.errors.TransportException;
25  import org.eclipse.jgit.internal.JGitText;
26  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
27  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
28  import org.eclipse.jgit.junit.TestRepository;
29  import org.eclipse.jgit.lib.ObjectId;
30  import org.eclipse.jgit.lib.Repository;
31  import org.eclipse.jgit.revwalk.RevCommit;
32  import org.eclipse.jgit.storage.pack.PackStatistics;
33  import org.eclipse.jgit.transport.BasePackFetchConnection.FetchConfig;
34  import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
35  import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
36  import org.eclipse.jgit.transport.resolver.UploadPackFactory;
37  import org.junit.After;
38  import org.junit.Before;
39  import org.junit.Test;
40  
41  public class TestProtocolTest {
42  	private static final RefSpec HEADS = new RefSpec("+refs/heads/*:refs/heads/*");
43  
44  	private static final RefSpec MASTER = new RefSpec(
45  			"+refs/heads/master:refs/heads/master");
46  
47  	private static final int HAVES_PER_ROUND = 32;
48  	private static final int MAX_HAVES = 256;
49  
50  	private static class User {
51  		private final String name;
52  
53  		private User(String name) {
54  			this.name = name;
55  		}
56  	}
57  
58  	private static class DefaultUpload implements UploadPackFactory<User> {
59  		@Override
60  		public UploadPack create(User req, Repository db) {
61  			UploadPack up = new UploadPack(db);
62  			up.setPostUploadHook((PackStatistics stats) -> {
63  				havesCount = stats.getHaves();
64  			});
65  			return up;
66  		}
67  	}
68  
69  	private static class DefaultReceive implements ReceivePackFactory<User> {
70  		@Override
71  		public ReceivePack create(User req, Repository db) {
72  			return new ReceivePack(db);
73  		}
74  	}
75  
76  	private static long havesCount;
77  
78  	private List<TransportProtocol> protos;
79  	private TestRepository<InMemoryRepository> local;
80  	private TestRepository<InMemoryRepository> remote;
81  
82    @Before
83  	public void setUp() throws Exception {
84  		protos = new ArrayList<>();
85  		local = new TestRepository<>(
86  				new InMemoryRepository(new DfsRepositoryDescription("local")));
87  		remote = new TestRepository<>(
88  				new InMemoryRepository(new DfsRepositoryDescription("remote")));
89    }
90  
91  	@After
92  	public void tearDown() {
93  		for (TransportProtocol proto : protos) {
94  			Transport.unregister(proto);
95  		}
96  	}
97  
98  	@Test
99  	public void testFetch() throws Exception {
100 		ObjectId master = remote.branch("master").commit().create();
101 
102 		TestProtocol<User> proto = registerDefault();
103 		URIish uri = proto.register(new User("user"), remote.getRepository());
104 
105 		try (Git git = new Git(local.getRepository())) {
106 			git.fetch()
107 					.setRemote(uri.toString())
108 					.setRefSpecs(HEADS)
109 					.call();
110 			assertEquals(master,
111 					local.getRepository().exactRef("refs/heads/master").getObjectId());
112 		}
113 	}
114 
115 	@Test
116 	public void testPush() throws Exception {
117 		ObjectId master = local.branch("master").commit().create();
118 
119 		TestProtocol<User> proto = registerDefault();
120 		URIish uri = proto.register(new User("user"), remote.getRepository());
121 
122 		try (Git git = new Git(local.getRepository())) {
123 			git.push()
124 					.setRemote(uri.toString())
125 					.setRefSpecs(HEADS)
126 					.call();
127 			assertEquals(master,
128 					remote.getRepository().exactRef("refs/heads/master").getObjectId());
129 		}
130 	}
131 
132 	@Test
133 	public void testFullNegotiation() throws Exception {
134 		TestProtocol<User> proto = registerDefault();
135 		URIish uri = proto.register(new User("user"), remote.getRepository());
136 
137 		// Enough local branches to cause 10 rounds of negotiation,
138 		// and a unique remote master branch commit with a later timestamp.
139 		for (int i = 0; i < 10 * HAVES_PER_ROUND; i++) {
140 			local.branch("local-branch-" + i).commit().create();
141 		}
142 		remote.tick(11 * HAVES_PER_ROUND);
143 		RevCommit master = remote.branch("master").commit()
144 				.add("readme.txt", "unique commit").create();
145 
146 		try (Git git = new Git(local.getRepository())) {
147 			assertNull(local.getRepository().exactRef("refs/heads/master"));
148 			git.fetch().setRemote(uri.toString()).setRefSpecs(MASTER).call();
149 			assertEquals(master, local.getRepository()
150 					.exactRef("refs/heads/master").getObjectId());
151 			assertEquals(10 * HAVES_PER_ROUND, havesCount);
152 		}
153 	}
154 
155 	@Test
156 	public void testMaxHaves() throws Exception {
157 		TestProtocol<User> proto = registerDefault();
158 		URIish uri = proto.register(new User("user"), remote.getRepository());
159 
160 		// Enough local branches to cause 10 rounds of negotiation,
161 		// and a unique remote master branch commit with a later timestamp.
162 		for (int i = 0; i < 10 * HAVES_PER_ROUND; i++) {
163 			local.branch("local-branch-" + i).commit().create();
164 		}
165 		remote.tick(11 * HAVES_PER_ROUND);
166 		RevCommit master = remote.branch("master").commit()
167 				.add("readme.txt", "unique commit").create();
168 
169 		TestProtocol.setFetchConfig(new FetchConfig(true, MAX_HAVES));
170 		try (Git git = new Git(local.getRepository())) {
171 			assertNull(local.getRepository().exactRef("refs/heads/master"));
172 			git.fetch().setRemote(uri.toString()).setRefSpecs(MASTER).call();
173 			assertEquals(master, local.getRepository()
174 					.exactRef("refs/heads/master").getObjectId());
175 			assertTrue(havesCount <= MAX_HAVES);
176 		}
177 	}
178 
179 	@Test
180 	public void testUploadPackFactory() throws Exception {
181 		ObjectId master = remote.branch("master").commit().create();
182 
183 		final AtomicInteger rejected = new AtomicInteger();
184 		TestProtocol<User> proto = registerProto((User req, Repository db) -> {
185 			if (!"user2".equals(req.name)) {
186 				rejected.incrementAndGet();
187 				throw new ServiceNotAuthorizedException();
188 			}
189 			return new UploadPack(db);
190 		}, new DefaultReceive());
191 
192 		// Same repository, different users.
193 		URIish user1Uri = proto.register(new User("user1"), remote.getRepository());
194 		URIish user2Uri = proto.register(new User("user2"), remote.getRepository());
195 
196 		try (Git git = new Git(local.getRepository())) {
197 			try {
198 				git.fetch()
199 						.setRemote(user1Uri.toString())
200 						.setRefSpecs(MASTER)
201 						.call();
202 				fail("accepted not permitted fetch");
203 			} catch (InvalidRemoteException expected) {
204 				// Expected.
205 			}
206 			assertEquals(1, rejected.get());
207 			assertNull(local.getRepository().exactRef("refs/heads/master"));
208 
209 			git.fetch()
210 					.setRemote(user2Uri.toString())
211 					.setRefSpecs(MASTER)
212 					.call();
213 			assertEquals(1, rejected.get());
214 			assertEquals(master,
215 					local.getRepository().exactRef("refs/heads/master").getObjectId());
216 		}
217 	}
218 
219 	@Test
220 	public void testReceivePackFactory() throws Exception {
221 		ObjectId master = local.branch("master").commit().create();
222 
223 		final AtomicInteger rejected = new AtomicInteger();
224 		TestProtocol<User> proto = registerProto(new DefaultUpload(),
225 				(User req, Repository db) -> {
226 					if (!"user2".equals(req.name)) {
227 						rejected.incrementAndGet();
228 						throw new ServiceNotAuthorizedException();
229 					}
230 					return new ReceivePack(db);
231 				});
232 
233 		// Same repository, different users.
234 		URIish user1Uri = proto.register(new User("user1"), remote.getRepository());
235 		URIish user2Uri = proto.register(new User("user2"), remote.getRepository());
236 
237 		try (Git git = new Git(local.getRepository())) {
238 			try {
239 				git.push()
240 						.setRemote(user1Uri.toString())
241 						.setRefSpecs(HEADS)
242 						.call();
243 				fail("accepted not permitted push");
244 			} catch (TransportException expected) {
245 				assertTrue(expected.getMessage().contains(
246 						JGitText.get().pushNotPermitted));
247 			}
248 			assertEquals(1, rejected.get());
249 			assertNull(remote.getRepository().exactRef("refs/heads/master"));
250 
251 			git.push()
252 					.setRemote(user2Uri.toString())
253 					.setRefSpecs(HEADS)
254 					.call();
255 			assertEquals(1, rejected.get());
256 			assertEquals(master,
257 					remote.getRepository().exactRef("refs/heads/master").getObjectId());
258 		}
259 	}
260 
261 	private TestProtocol<User> registerDefault() {
262 		return registerProto(new DefaultUpload(), new DefaultReceive());
263 	}
264 
265 	private TestProtocol<User> registerProto(UploadPackFactory<User> upf,
266 			ReceivePackFactory<User> rpf) {
267 		TestProtocol<User> proto = new TestProtocol<>(upf, rpf);
268 		protos.add(proto);
269 		Transport.register(proto);
270 		return proto;
271 	}
272 }