View Javadoc
1   /*
2    * Copyright (C) 2018, Google LLC.
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  package org.eclipse.jgit.transport;
44  
45  import static org.hamcrest.Matchers.containsString;
46  import static org.hamcrest.Matchers.hasItems;
47  import static org.junit.Assert.assertEquals;
48  import static org.junit.Assert.assertThat;
49  import static org.junit.Assert.assertTrue;
50  
51  import java.io.ByteArrayInputStream;
52  import java.io.ByteArrayOutputStream;
53  import java.io.IOException;
54  import java.util.Collection;
55  import java.util.List;
56  import java.util.stream.Collectors;
57  
58  import org.eclipse.jgit.errors.PackProtocolException;
59  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
60  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
61  import org.eclipse.jgit.junit.TestRepository;
62  import org.eclipse.jgit.lib.Config;
63  import org.eclipse.jgit.lib.ObjectId;
64  import org.eclipse.jgit.revwalk.RevCommit;
65  import org.junit.Before;
66  import org.junit.Rule;
67  import org.junit.Test;
68  import org.junit.rules.ExpectedException;
69  
70  public class ProtocolV2ParserTest {
71  
72  	@Rule
73  	public ExpectedException thrown = ExpectedException.none();
74  
75  	private TestRepository<InMemoryRepository> testRepo;
76  
77  	@Before
78  	public void setUp() throws Exception {
79  		testRepo = new TestRepository<>(newRepo("protocol-v2-parser-test"));
80  	}
81  
82  	private static InMemoryRepository newRepo(String name) {
83  		return new InMemoryRepository(new DfsRepositoryDescription(name));
84  	}
85  
86  	private static class ConfigBuilder {
87  
88  		private boolean allowRefInWant;
89  
90  		private boolean allowFilter;
91  
92  		private ConfigBuilder() {
93  		}
94  
95  		static ConfigBuilder start() {
96  			return new ConfigBuilder();
97  		}
98  
99  		static TransferConfig getDefault() {
100 			return start().done();
101 		}
102 
103 		ConfigBuilder allowRefInWant() {
104 			allowRefInWant = true;
105 			return this;
106 		}
107 
108 		ConfigBuilder allowFilter() {
109 			allowFilter = true;
110 			return this;
111 		}
112 
113 		TransferConfig done() {
114 			Config rc = new Config();
115 			rc.setBoolean("uploadpack", null, "allowrefinwant", allowRefInWant);
116 			rc.setBoolean("uploadpack", null, "allowfilter", allowFilter);
117 			return new TransferConfig(rc);
118 		}
119 	}
120 
121 	/*
122 	 * Convert the input lines to the PacketLine that the parser reads.
123 	 */
124 	private static PacketLineIn formatAsPacketLine(String... inputLines)
125 			throws IOException {
126 		ByteArrayOutputStream send = new ByteArrayOutputStream();
127 		PacketLineOut pckOut = new PacketLineOut(send);
128 		for (String line : inputLines) {
129 			if (line == PacketLineIn.END) {
130 				pckOut.end();
131 			} else if (line == PacketLineIn.DELIM) {
132 				pckOut.writeDelim();
133 			} else {
134 				pckOut.writeString(line);
135 			}
136 		}
137 
138 		return new PacketLineIn(new ByteArrayInputStream(send.toByteArray()));
139 	}
140 
141 	private static List<String> objIdsAsStrings(Collection<ObjectId> objIds) {
142 		// TODO(ifrade) Translate this to a matcher, so it would read as
143 		// assertThat(req.wantsIds(), hasObjectIds("...", "..."))
144 		return objIds.stream().map(ObjectId::name).collect(Collectors.toList());
145 	}
146 
147 	/*
148 	 * Succesful fetch with the basic core commands of the protocol.
149 	 */
150 	@Test
151 	public void testFetchBasicArguments()
152 			throws PackProtocolException, IOException {
153 		PacketLineIn pckIn = formatAsPacketLine(
154 				PacketLineIn.DELIM,
155 				"thin-pack", "no-progress", "include-tag", "ofs-delta",
156 				"want 4624442d68ee402a94364191085b77137618633e",
157 				"want f900c8326a43303685c46b279b9f70411bff1a4b",
158 				"have 554f6e41067b9e3e565b6988a8294fac1cb78f4b",
159 				"have abc760ab9ad72f08209943251b36cb886a578f87", "done",
160 				PacketLineIn.END);
161 		ProtocolV2Parser parser = new ProtocolV2Parser(
162 				ConfigBuilder.getDefault());
163 		FetchV2Request request = parser.parseFetchRequest(pckIn,
164 				testRepo.getRepository().getRefDatabase());
165 		assertTrue(request.getOptions()
166 				.contains(GitProtocolConstants.OPTION_THIN_PACK));
167 		assertTrue(request.getOptions()
168 				.contains(GitProtocolConstants.OPTION_NO_PROGRESS));
169 		assertTrue(request.getOptions()
170 				.contains(GitProtocolConstants.OPTION_INCLUDE_TAG));
171 		assertTrue(request.getOptions()
172 				.contains(GitProtocolConstants.CAPABILITY_OFS_DELTA));
173 		assertThat(objIdsAsStrings(request.getWantsIds()),
174 				hasItems("4624442d68ee402a94364191085b77137618633e",
175 						"f900c8326a43303685c46b279b9f70411bff1a4b"));
176 		assertThat(objIdsAsStrings(request.getPeerHas()),
177 				hasItems("554f6e41067b9e3e565b6988a8294fac1cb78f4b",
178 						"abc760ab9ad72f08209943251b36cb886a578f87"));
179 		assertTrue(request.getWantedRefs().isEmpty());
180 		assertTrue(request.wasDoneReceived());
181 	}
182 
183 	@Test
184 	public void testFetchWithShallow_deepen() throws IOException {
185 		PacketLineIn pckIn = formatAsPacketLine(
186 				PacketLineIn.DELIM,
187 				"deepen 15",
188 				"deepen-relative",
189 				"shallow 28274d02c489f4c7e68153056e9061a46f62d7a0",
190 				"shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d",
191 				PacketLineIn.END);
192 		ProtocolV2Parser parser = new ProtocolV2Parser(
193 				ConfigBuilder.getDefault());
194 		FetchV2Request request = parser.parseFetchRequest(pckIn,
195 				testRepo.getRepository().getRefDatabase());
196 		assertThat(objIdsAsStrings(request.getClientShallowCommits()),
197 				hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0",
198 						"145e683b229dcab9d0e2ccb01b386f9ecc17d29d"));
199 		assertTrue(request.getDeepenNotRefs().isEmpty());
200 		assertEquals(15, request.getDepth());
201 		assertTrue(request.getOptions()
202 				.contains(GitProtocolConstants.OPTION_DEEPEN_RELATIVE));
203 	}
204 
205 	@Test
206 	public void testFetchWithShallow_deepenNot() throws IOException {
207 		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
208 				"shallow 28274d02c489f4c7e68153056e9061a46f62d7a0",
209 				"shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d",
210 				"deepen-not a08595f76159b09d57553e37a5123f1091bb13e7",
211 				PacketLineIn.END);
212 		ProtocolV2Parser parser = new ProtocolV2Parser(
213 				ConfigBuilder.getDefault());
214 		FetchV2Request request = parser.parseFetchRequest(pckIn,
215 				testRepo.getRepository().getRefDatabase());
216 		assertThat(objIdsAsStrings(request.getClientShallowCommits()),
217 				hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0",
218 						"145e683b229dcab9d0e2ccb01b386f9ecc17d29d"));
219 		assertThat(request.getDeepenNotRefs(),
220 				hasItems("a08595f76159b09d57553e37a5123f1091bb13e7"));
221 	}
222 
223 	@Test
224 	public void testFetchWithShallow_deepenSince() throws IOException {
225 		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
226 				"shallow 28274d02c489f4c7e68153056e9061a46f62d7a0",
227 				"shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d",
228 				"deepen-since 123123123",
229 				PacketLineIn.END);
230 		ProtocolV2Parser parser = new ProtocolV2Parser(
231 				ConfigBuilder.getDefault());
232 		FetchV2Request request = parser.parseFetchRequest(pckIn,
233 				testRepo.getRepository().getRefDatabase());
234 		assertThat(objIdsAsStrings(request.getClientShallowCommits()),
235 				hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0",
236 						"145e683b229dcab9d0e2ccb01b386f9ecc17d29d"));
237 		assertEquals(123123123, request.getDeepenSince());
238 	}
239 
240 	@Test
241 	public void testFetchWithNoneFilter() throws IOException {
242 		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
243 				"filter blob:none",
244 				PacketLineIn.END);
245 		ProtocolV2Parser parser = new ProtocolV2Parser(
246 				ConfigBuilder.start().allowFilter().done());
247 		FetchV2Request request = parser.parseFetchRequest(pckIn,
248 				testRepo.getRepository().getRefDatabase());
249 		assertEquals(0, request.getFilterBlobLimit());
250 	}
251 
252 	@Test
253 	public void testFetchWithBlobSizeFilter() throws IOException {
254 		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
255 				"filter blob:limit=15",
256 				PacketLineIn.END);
257 		ProtocolV2Parser parser = new ProtocolV2Parser(
258 				ConfigBuilder.start().allowFilter().done());
259 		FetchV2Request request = parser.parseFetchRequest(pckIn,
260 				testRepo.getRepository().getRefDatabase());
261 		assertEquals(15, request.getFilterBlobLimit());
262 	}
263 
264 	@Test
265 	public void testFetchMustNotHaveMultipleFilters() throws IOException {
266 		thrown.expect(PackProtocolException.class);
267 		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
268 				"filter blob:none",
269 				"filter blob:limit=12",
270 				PacketLineIn.END);
271 		ProtocolV2Parser parser = new ProtocolV2Parser(
272 				ConfigBuilder.start().allowFilter().done());
273 		FetchV2Request request = parser.parseFetchRequest(pckIn,
274 				testRepo.getRepository().getRefDatabase());
275 		assertEquals(0, request.getFilterBlobLimit());
276 	}
277 
278 	@Test
279 	public void testFetchFilterWithoutAllowFilter() throws IOException {
280 		thrown.expect(PackProtocolException.class);
281 		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
282 				"filter blob:limit=12", PacketLineIn.END);
283 		ProtocolV2Parser parser = new ProtocolV2Parser(
284 				ConfigBuilder.getDefault());
285 		parser.parseFetchRequest(pckIn,
286 				testRepo.getRepository().getRefDatabase());
287 	}
288 
289 	@Test
290 	public void testFetchWithRefInWant() throws Exception {
291 		RevCommit one = testRepo.commit().message("1").create();
292 		RevCommit two = testRepo.commit().message("2").create();
293 		testRepo.update("branchA", one);
294 		testRepo.update("branchB", two);
295 
296 		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
297 				"want e4980cdc48cfa1301493ca94eb70523f6788b819",
298 				"want-ref refs/heads/branchA",
299 				PacketLineIn.END);
300 		ProtocolV2Parser parser = new ProtocolV2Parser(
301 				ConfigBuilder.start().allowRefInWant().done());
302 
303 
304 		FetchV2Request request = parser.parseFetchRequest(pckIn,
305 				testRepo.getRepository().getRefDatabase());
306 		assertEquals(1, request.getWantedRefs().size());
307 		assertThat(request.getWantedRefs().keySet(),
308 				hasItems("refs/heads/branchA"));
309 		assertEquals(2, request.getWantsIds().size());
310 		assertThat(objIdsAsStrings(request.getWantsIds()),
311 				hasItems("e4980cdc48cfa1301493ca94eb70523f6788b819",
312 						one.getName()));
313 	}
314 
315 	@Test
316 	public void testFetchWithRefInWantUnknownRef() throws Exception {
317 		thrown.expect(PackProtocolException.class);
318 		thrown.expectMessage(containsString("refs/heads/branchC"));
319 
320 		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
321 				"want e4980cdc48cfa1301493ca94eb70523f6788b819",
322 				"want-ref refs/heads/branchC",
323 				PacketLineIn.END);
324 		ProtocolV2Parser parser = new ProtocolV2Parser(
325 				ConfigBuilder.start().allowRefInWant().done());
326 
327 		RevCommit one = testRepo.commit().message("1").create();
328 		RevCommit two = testRepo.commit().message("2").create();
329 		testRepo.update("branchA", one);
330 		testRepo.update("branchB", two);
331 
332 		parser.parseFetchRequest(pckIn,
333 				testRepo.getRepository().getRefDatabase());
334 	}
335 
336 }